So it finally happened, you've been writing your NestJS server application and everything is going great, and you get a message like this:
[Nest] 116557 - 10/07/2022, 2:12:40 PM ERROR [ExceptionHandler] Nest cannot create the BarModule instance.
The module at index [0] of the BarModule "imports" array is undefined.
Potential causes:
- A circular dependency between modules. Use forwardRef() to avoid it. Read more: https://docs.nestjs.com/fundamentals/circular-dependency
- The module at index [0] is of type "undefined". Check your import statements and the type of the module.
Scope [AppModule -> FooModule]
You take a moment, analyze it, and realize that maybe you just need to add a forwardRef
to your imports
of your BarModule
and FooModule
and it'll go away, right?
Nest can't resolve dependencies of the FooService (?). Please make sure that the argument dependency at index [0] is available in the FooModule context.
Potential solutions:
- If dependency is a provider, is it part of the current FooModule?
- If dependency is exported from a separate @Module, is that module imported within FooModule?
@Module({
imports: [ /* the Module containing dependency */ ]
})
What the heck is dependency
? Why can't Nest tell you what's missing here? To answer those questions, let's talk first about what constitutes a circular dependency and the different kinds of circular dependencies that can show up, along with how to avoid them from happening in the first place.
What is a Circular Dependency
By definition, a circular dependency is a self-driven dependency. This means that for an object, a file, a class, etc, there is a dependency on the thing itself, through some import or instantiation chain, that causes the resolution of the import or instantiation to never be finished. A quick example (this can be run in the Node REPL or browser console):
const obj = {};obj.a = 'Hello World!';obj.b = { c: obj };console.log(obj);// <ref *1> { a: 'Hello World!', b: { c: [Circular *1] } }
Notice the [Circular *1]
in the commented line, that shows that obj
is circular upon itself.
Circular Dependency Types
As mentioned earlier there are a few different types of circular dependencies. These three are the most common that we run into with a NestJS application
- circular file imports - where
a.ts.
importb.ts
which importsa.ts
- circular module imports - where
FooModule
importsBarModule
importFooModule
, like the above error - circular constructors - where
FooService
injectsBarService
which injectsFooService
Note: these do not have to be immediately circular, you could have a full import chain of
a
importsb
importsc
importsd
importsa
which creates the circular dependency. Be vigilant in watching your imports and injections.
Most of the time, when we have the latter two we also have the former, which is where the forwardRef
comes in handy. It's a lazy evaluation tool, that allows the files to have these circular file imports while being able to resolve the class reference at runtime, because it's not the instance that we need, just the reference so we can refer back to the provider storage of the Dependency Injection system.
Barrel Files
A very common case for a circular file import that is usually unseen is when you are using barrel files, index.ts
files, to export multiple parts of your directory.
src/
foo/
foo.controller.ts
foo.module.ts
foo.service.ts
index.ts
The above structure gives us the ability to import { FooService } from '../foo'
assuming that index.ts
has export * from './foo.service'
in it, which is a nice improvement from having to go to each file. However, this power should be used with some caution, as an import to ../foo
really means an import to every file that src/foo/index.ts
mentions. That is to say, Typescript does not do any tree shaking on the barrel imports at compile time, so if one of the imports of the index.ts
imports another file that imports ../foo
then you've created a circular dependency without meaning to.
Module Imports
This is pretty much just like it sounds, like the example error above we have one module import another module which imports the first module again. And just as the note above, this does not have to be a single level deep, it can spread through the whole application if you're unlucky enough. The reason that this becomes a problem is because of the circular file import mentioned above. foo.module
can't import bar.module
because bar.module
imports foo.module
and JavaScript can't get one of the two of these created without the other. That's why we put off the delegation with a lazy evaluator.
Note: the other reason this becomes a problem relies on an advanced knowledge of decorators, which has a blog post eventually coming. Stay tuned.
Constructor Injections
Just like module imports, services that rely on other services that rely on the first become cyclic in their imports. When this happens, you get the "Nest cannot resolve dependency" error above. The reason that there's no name for the provider here is that it evaluates to unknown
due to the cyclic imports. When Nest sees an unknown
token, it calls that token dependency
. Once again, this is quickly solved with a lazy evaluator, this time inside an @Inject()
decorator to ensure the metadata for the circular dependency is correct.
How to find Circular Dependencies in your NestJS applications
Whew, okay, lots of talk on what circular dependencies are, but not yet on how to find them. Enter: madge
. Madge is a CLI tool for generating graphs of your module dependencies and fortunately has a flag for looking specifically for circular dependencies. First, install madge
as a devDependency
npm i -D madgeyarn add -D madgepnpm i -D madge
Next, run it with the --circular
flag against your application entry point
npx madge --circular src/main.tsyarn madge --circular src/main.tspnpm madge --circular src/main.ts
Then, profit! You now know where your circular files are
Processed 10 files (368ms) (2 warnings)
✖ Found 2 circular dependencies!
1) bar/bar.service.ts > foo/foo.service.ts
2) bar/bar.module.ts > foo/foo.module.ts
And it can find longer chains too, ensuring that all circular dependencies are taken care of.
Why to Avoid Circular Dependencies
Okay, okay, we've talked about what they are, and how to find them, but let's talk about why they should be avoided:
Circular dependencies are usually a code smell that code is too tightly coupled together. This often happens when we get too caught up in DRYing the code and not remembering that WET (write everything twice) code is okay. Tightly coupled code is also often a lack of foresight and planning for the modules and features being created. Remember that usually a module should be able to be picked up from it's current application, dropped in another NestJS application, and be able to function as is.* with the possible exception of having to fix up database connections/tables
Having circular dependencies in your code also makes it prone to race conditions. Nest evaluates dependencies in parallel and with no guarantee on when each dependency will be ready. In the case of REQUEST
scoped circular dependencies, you can properly have all the dependencies using @Inject(forwardRef(() => OtherProvider)
and still get an undefined
due to needing to call process.nextTick()
to populate the circular providers, due to the way that the dependencies get handled at request time.
If you notice that you have a circular dependency, take a moment to stop and think about if there's a better way to split up the code, the providers, and the feature itself, and find a way that doesn't require using a lazy evaluator, even if it means having the code written twice. If there's a single method that both services need, see if it can be moved to its own provider and utility type module.
The forwardRef
is there as your last resort, if there's absolutely no way around it, and it's powerful enough to get you out of the errors reported, but it shouldn't be used as a catch all.
Learn NestJS - Official NestJS Courses 📚
Level-up your NestJS and Node.js ecosystem skills in these incremental workshop-style courses, from the NestJS Creator himself, and help support the NestJS framework! 🐈🚀 The NestJS Fundamentals Course is now LIVE and 25% off for a limited time!
🎉 NEW - NestJS Course Extensions now live!
- NestJS Advanced Concepts Course now LIVE!
- NestJS Authentication / Authorization Course now LIVE!
- NestJS GraphQL Course (code-first & schema-first approaches) are now LIVE!
- NestJS Authentication / Authorization Course now LIVE!