Circular Dependencies in NestJS and how to Avoid Them.

Jay McDoniel | Trilon Consulting
Jay McDoniel

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: - 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 */ ] })
Well yes, but actually no

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 };
// <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. import b.ts which imports a.ts
  • circular module imports - where FooModule imports BarModule import FooModule, like the above error
  • circular constructors - where FooService injects BarService which injects FooService

Note: these do not have to be immediately circular, you could have a full import chain of a imports b imports c imports d imports a 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 madge
yarn add -D madge
pnpm i -D madge

Next, run it with the --circular flag against your application entry point

npx madge --circular src/main.ts
yarn madge --circular src/main.ts
pnpm 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!

Share this Post!

📬 Trilon Newsletter

Stay up to date with all the latest Articles & News!

More from the Trilon Blog .

Jay McDoniel | Trilon Consulting
Jay McDoniel

NestJS Metadata Deep Dive

In this article we'll be doing a deep-dive and learning about how NestJS uses Metadata internally for everything from dependency injection, to decorators we use everyday!

Read More
Kamil Mysliwiec | Trilon Consulting
Kamil Mysliwiec

NestJS v10 is now available

Today I am excited to announce the official release of Nest 10: A progressive Node.js framework for building efficient and enterprise-grade, server-side applications.

Read More
Manuel Carballido | Trilon Consulting
Manuel Carballido

Implementing data source agnostic services with NestJS

Learn how to implement data source logic in an agnostic way in yours NestJS applications.

Read More

What we do at Trilon .

At Trilon, our goal is to help elevate teams - giving them the push they need to truly succeed in today's ever-changing tech world.

Trilon - Consulting

Consulting .

Let us help take your Application to the next level - planning the next big steps, reviewing architecture, and brainstorming with the team to ensure you achieve your most ambitious goals!

Trilon - Development and Team Augmentation

Development .

Trilon can become part of your development process, making sure that you're building enterprise-grade, scalable applications with best-practices in mind, all while getting things done better and faster!

Trilon - Workshops on NestJS, Node, and other modern JavaScript topics

Workshops .

Have a Trilon team member come to YOU! Get your team up to speed with guided workshops on a huge variety of topics. Modern NodeJS (or NestJS) development, JavaScript frameworks, Reactive Programming, or anything in between! We've got you covered.

Trilon - Open-source contributors

Open-source .

We love open-source because we love giving back to the community! We help maintain & contribute to some of the largest open-source projects, and hope to always share our knowledge with the world!

Explore more

Write us a message .

Let's talk about how Trilon can help your next project get to the next level.

Rather send us an email? Write to:
© 2019-2023 Trilon.