Circular required modules in node

I noticed the classic “undefined is not a function” error occuring in suprising places. Here is an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
// responses.js
var forms = require('./forms');
var responses = {
formatResponse(response) {
forms.getForm(response.formId).then(function(form) {
// do stuff with form and the response
});
}
}

The undefined function is forms.getForm. This was odd because 1. forms clearly contained that function, and 2. That code passed my test suite. The “undefined is not a function” error was only happening when using the app.

As it turns out, the problem was a circular dependency. responses requires forms, but forms also requires responses. I had no idea you couldn’t do this. I agreed that it was a bit of a code smell to have a circular dependency like that, but it could be done right? Well, no.

The node docs explain this briefly, and explain that in order to prevent an infinite loop, an unfinished copy of the exported object can be returned. This is what I experienced. The forms variable was an empty object.

Solutions

Move the require statement to where it’s needed

The easiest solution is to move the require statement into the function that uses it. The above code would become:

1
2
3
4
5
6
7
8
9
10
11
var responses = {
formatResponse(response) {
var forms = require('./forms');
forms.getForm(response.formId).then(function(form) {
// do stuff with form and the response
});
}
}

To me that looks really ugly. What if you make a lot of use of that module? You’d have to include it in each function. Also, I like glancing at the top of a file and knowing what modules the file depends upon. This is possibly inefficient as well.

Or, change your architecture

Just remove the circular logic. Simple right? Here’s how I did it.

Initial State

This clearly wasn’t going to fly. I decided to move my “formatting” logic out of responses. This makes sense. But here’s what I ended up with:

Still Circular

Still circular. One feature in particular connected the reponses to the formatter. What if I pull that out?

Just a bigger circle

So now I have a bigger circle. That’s all I’ve been doing. I decided I needed to be orchestraing these modules’ connections from an outside place. The router made sense.

It works

The two colors represent two different routes. It’s only doing one thing from each of the modules, but still, I don’t like having large routers.

More abstraction

I could make a new “manager” type module. But I’ve found that “manager” type structures just make your code that much harder to reason about. So I decided to leave it as is.

In summary, circular dependency is bad in node. Keep that in mind as you design your architecture.

avatar

Dev Blog