API with NestJS #61. Dealing with circular dependencies

NestJS

This entry is part 61 of 85 in the API with NestJS

We need to watch out for quite a few pitfalls when designing our architecture. One of them is the possibility of circular dependencies. In this article, we go through this concept in the context of Node.js modules and NestJS services.

Circular dependencies in Node.js modules

A circular dependency between Node.js modules happens when two files import each other. Let’s look into this straightforward example:

one.js

two.js

After running , we see the following output:

file two.js: undefined 2
file one.js: 1 2
(node:109498) Warning: Accessing non-existent property ‘one’ of module exports inside circular dependency
(Use node --trace-warnings ... to show where the warning was created)

We can see that a code containing circular dependencies can run but might result in unexpected results. The above code executes as follows:

  • executes, and imports ,
  • executes, and imports ,
  • to prevent an infinite loop, loads an unfinished copy of ,
    • this is why the variable in is undefined,
  • finishes executing, and its exported value reaches ,
  • continues running and contains all valid values.

The above example allows us to understand how Node.js reacts to circular dependencies. Circular dependencies are often a sign of a bad design, and we should avoid them when possible.

Detecting circular dependencies using ESLint

The provided code example makes it very obvious that there is a circular dependency between files. It is not always that apparent, though. Fortunately, ESLint can help us detect such dependencies. To do that, we need the eslint-plugin-import package.

The rule that we want is called , and it ensures that no circular dependencies are present between our files.

In our NestJS project, we would set the configuration in the following way:

.eslintrc.js

Circular dependencies in NestJS

Besides circular dependencies between Node.js modules, we might also run into this issue when working with NestJS modules. In part 55 of this series, we’ve implemented a feature of uploading files to the server. Let’s expand on it to create a case with a circular dependency.

localFiles.service.js

users.service.js

Solving the issue using forward referencing

In our case, the needs the and the other way around. Let’s look into how our modules look so far.

localFiles.module.ts

users.module.ts

Above, we see that imports the and vice versa. Running the application with the above configuration causes an error, unfortunately.

[ExceptionHandler] Nest cannot create the LocalFilesModule instance.
The module at index [2] of the LocalFilesModule “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 [2] is of type “undefined”. Check your import statements and the type of the module.

Scope [AppModule -> PostsModule -> UsersModule]
Error: Nest cannot create the LocalFilesModule instance.
The module at index [2] of the LocalFilesModule “imports” array is undefined.

workaround for the above is to use forward referencing. Thanks to it, we can refer to a module before NestJS initializes it. To do that, we need to use the function.

localFiles.module.ts

users.module.ts

Doing the above solves the issue of circular dependencies between our modules. Unfortunately, we still need to fix the problem for services. We need to use the function and the decorator to do that.

localFiles.service.ts

users.service.ts

Doing all of the above causes our services to function correctly despite the circular dependencies.

Circular dependencies between TypeORM entities

We might also run into issues with circular dependencies with TypeORM entities. For example, this might happen when dealing with relationships.

If you want to know more about relationships with TypeORM and NestJS, check out API with NestJS #7. Creating relationships with Postgres and TypeORM

Fortunately, people noticed this problem, and there is a solution. For a whole discussion, check out this issue on GitHub.

Avoiding circular dependencies in our architecture

Unfortunately, having circular dependencies in our modules is often a sign of a design worth improving. In our case, we violate the single responsibility principle of SOLID. Our and are responsible for multiple functionalities.

If you want to know more about SOLID, check out Applying SOLID principles to your TypeScript code

We can create a service that encapsulates the functionalities that would otherwise cause the circular dependency issue.

userAvatars.service.ts

We can now use the above service straight in our  or create a brand new controller just for the new service.

users.controller.ts

Summary

In this article, we’ve looked into the issue of circular dependencies in the context of Node.js and NestJS. We’ve learned how Node.js deals with circular dependencies and how it can lead to problems that might be difficult to predict. We’ve also dealt with circular dependencies across NestJS using forward referencing. Since that is just a workaround and circular dependencies can signify a lacking design, we rewrote our code to eliminate it. That is usually the best approach, and we should avoid introducing circular dependencies to our architecture.

Series Navigation<< API with NestJS #60. The OpenAPI specification and SwaggerAPI with NestJS #62. Introduction to MikroORM with PostgreSQL >>
Subscribe
Notify of
guest
1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
name
name
8 months ago

create function undefined in authentication service