Identify Circular Dependencies in NestJs : Using madge
Introduction
NestJs has become one of the most popular and progressive framework of nodejs. Nodejs and other libraries have existed for many years but none of them was able to solve the problems related to Architecture. NestJS provides an out-of-the-box architectural framework that might be familiar to those who have experience with concepts from Angular or a little bit of Java(Spring). If you’re acquainted with the architectural principles, you’ll likely find NestJS’ architecture to be quite recognizable.
In NestJS, there’s a concept known as “providers.” These providers can be injected as dependencies within the framework, and NestJS handles the wiring process dynamically at runtime.
Circular Dependency Issue
Circular Dependency arises if Class A requires Class B and at the same time Class B requires Class A.
Circular dependency generally arises in providers in NestJs. Sooner or later as the project grows and is not designed properly at the beginning, people tend to define functionalities as and wherever required thereby causing circular dependency. It is one of the most common problems that arise in nestjs.
NestJs throws the error of circular dependency as the application bootstraps itself.
Identifying Circular Dependency
When it comes to designing our codebase, it’s essential to create a structure that avoids circular dependencies. However, as the application expands, tracking relationships between various entities can become challenging.
A CLI tool called madge
comes to the rescue in this situation.
Madge is a cli-tool that identifies dependencies between the files. It can be used on cli as well it can generate a visual graph to show all the module dependencies.
For showing visual dependencies, we can use graphviz
Installation
npm -g install madge
sudo apt-get install graphviz #( On Ubuntu )
Generate Dependency Graph
madge --image graph.png --extensions ts src
--image graph.png
: Generate the dependent graph image as .png file
--extensions ts
: Only searches for dependencies in typescript files(.ts)
src
: Searches in the folder ./src , We can pass multiple space separated folders.
The above command directly generates an image of the dependency graph.
If you want to see only circular dependencies pass
--circular
as a parameter.
madge --image graph.png --extensions ts --circular src
Blue Color show the files that has dependecies, Green color nodes does not have any dependencies and Red color nodes has Circular dependecies
* Using Graphviz ( For larger applications with too many files)
Since the file generated here is a PNG file, when there are too many files in the application, understanding and observing the PNG file becomes difficult.
We can use graphviz tool which is an open source graph visualization software. It uses DOT language scripts which has the file name extension “.gv”.
* Generating .gv file using madge
madge --dot --extensions ts src > graph.gv
This will generate a graph.gv in the current folder.
We can now either copy & paste the contents of this file into an online graphviz visualizer or you can install a VSCode Extension that shows the live preview of your .gv files. (To open, Press Ctrl+Shift+p -> type > graphviz preview)
Refactor and Resolve
Once we have identified the issue, we can now refactor our codebase to solve the circular dependency
- We can use NestJs dependency injection ( ForwardRef ) to solve the issue
- We can reorganize our modules to separate the concern
- Create shared modules for common functionality to avoid inter-module dependencies.
Conclusion
Circular dependency generally occurs when code is tightly coupled. From past experience, I have seen that when we try not to duplicate code ( DRY) we end up in such a situation.
We should often design independent core services that take basic inputs and return some DTOs. We should then orchestrate them in other services/controllers even though we have to call two or more services to get a final output. Merging them and creating another service often leads to problems.
If a circular dependency arises, we should think of splitting up the code, moving our code into different features
module, and building up core module with the services. If there’s a single method that is used in both modules see if we can move it to a utility module. Generally, I don't prefer creating a generic utility class like utils.js ( create specific utility classes like StringHelper.js, LeadDuplicateHelper.js)
Happy Coding !! Cheers 👋