The author selected Free and Open Source Fund to receive a donation as part of the Write for DOnations program.
NestJS throws this error whenever it detects circular dependency between modules. In this tutorial, you will learn what circular dependency in NestJS is and how you can resolve it.
Circular Dependency occurs when two classes depend on each other. In NestJS, circular dependency can occur between modules and providers. There are different types of circular dependencies and the most common ones that you can run into during development are:
In this article, you will work with a demo e-commerce application comprising two modules: OrdersModule
and PaymentModule
. Within these two modules are their providers OrdersService
and PaymentService
. The OrderService
initiates the payment process for an order, while the PaymentService
needs to update the order status once payment has been processed. This will create circular dependency as module and service level as both resources depend on each other.
Note: To bootstrap your Nest project, run the following command:
nest new circular-dependency
Then run nest g resource orders
and nest g resource payment
to generate a new resource for orders and payment that will create a module, controller, and service files.
To follow this tutorial, you will need:
First, here’s a closer look at the error that is thrown in a situation where OrdersModule
and PaymentModule
depend on each other.
Nest cannot create the PaymentModule instance.
The module at index [0] of the PaymentModule "imports" array is undefined.
In NestJS, modules can depend on each other, but when you have two modules directly importing each other, it creates a circular dependency, which NestJS cannot resolve. This means that each module is waiting for the other to be fully initialized before it can itself be initialized, leading to a deadlock.
When NestJS starts the application, it first resolves all the modules and their dependencies before creating instances of those modules. Now, NestJs looks through the imports array in the root module app.module.ts
and starts to resolve OrderModule
. It then sees that it depends on PaymentModule
. NestJS pauses the resolution of the OrdersModule
to resolve this dependency first because PaymentModule
is in the imports array of OrdersModule
. When NestJS switches to the PaymentModule
, it looks at its import and sees that OrdersModule
is listed. Notice that OrdersModule
has not been fully resolved yet, NestJS then attempts to initialize OrdersModule
as part of resolving PaymentModule
. It goes back to initializing and resolving the OrdersModule
and it ends up in a loop because OrdersModule
is waiting for PaymentsModule
to be initialized and PaymentModule
is waiting for OrdersModule
to be initialized.
Assuming PaymentModule
is the only dependency of OrdersModule
and not vice versa, during PaymentModule
resolve as explained above, NesJS will look at its imports array for other modules to be resolved and instantiate the PaymentModule
since OrderModule
is not part of its imports array.
To resolve circular dependencies between modules, we will use the forwardRef
utility function on both OrderModule
and PaymentModule
.
import { Module, forwardRef } from "@nestjs/common";
import { PaymentService } from "./payment.service";
import { PaymentController } from "./payment.controller";
import { OrdersModule } from "../orders/orders.module";
@Module({
imports: [forwardRef(() => OrdersModule)],
controllers: [PaymentController],
providers: [PaymentService],
exports: [PaymentService],
})
export class PaymentModule {}
import { Module, forwardRef } from "@nestjs/common";
import { OrdersService } from "./orders.service";
import { OrdersController } from "./orders.controller";
import { PaymentModule } from "../payment/payment.module";
@Module({
imports: [forwardRef(() => PaymentModule)],
controllers: [OrdersController],
providers: [OrdersService],
exports: [OrdersService],
})
export class OrdersModule {}
Save your code and start up the terminal again. The error should be cleared now:
In the code above, the forwardRef
is used to wrap the imports on both sides to ensure that both modules can be loaded without immediately resolving their dependencies on each other.
At the backend, NestJS starts the module initialization process and when it sees the forwardRef
in either module’s imports array, it will not immediately attempt to resolve both modules, rather it acknowledges the dependency but delays its resolution. Both OrdersModule
and PaymentModule
are loaded into the application’s context without their dependencies being fully resolved to prevent the deadlock. NestJS will go back to the deferred dependencies once all modules are loaded. Now both OrdersModule
& PaymentModule
have been loaded (not fully initialized), NestJS calls the forwardRef
function to resolve all dependencies.
In the previous section, you learned how to resolve circular dependency that occurs at the module level. The second scenario is: in OrdersService
you create an order, save it to the database, and then immediately call the processPayment
method to process a user’s order. And, in PaymentService
, once the processPayment
method is called, it immediately calls the updateOrderStatus
method to update the order status to successful
or any status you might want to denote it with.
Inject these dependencies in both services:
interface IOrder {
id: number;
customerName: string;
item: string;
orderDate: Date;
totalAmount: number;
}
@Injectable()
export class OrdersService {
constructor(private readonly paymentService: PaymentService) {}
async getAllOrders(): Promise<IOrder[]> {
const mockOrders: IOrder[] = [
{
id: 1,
customerName: 'Taofiq',
item: 'Airpod',
orderDate: new Date(),
totalAmount: 900,
},
];
return mockOrders;
}
async createOrder(createOrderDTO: CreateOrderDTO): Promise<any> {
// mock data to simulate order creation
const newOrder: any = {
id: Math.floor(Math.random() * 100) + 1,
...createOrderDTO,
orderDate: new Date(createOrderDTO.orderDate),
};
await this.paymentService.processPayment(newOrder.id);
return newOrder;
}
async updateOrderStatus(orderId: string, status: string) {
// update the order status here in the database
console.log(`Order ${orderId} status updated to ${status}`);
}
}
@Injectable()
export class PaymentService {
constructor(private readonly ordersService: OrdersService) {}
async processPayment(orderId: string) {
// In a real scenario, you would interact with a payment gateway.
console.log(`Processing payment for order ${orderId}`);
const paymentSuccessful = true;
if (paymentSuccessful) {
// Once payment is successful, update the order status to "Paid"
await this.ordersService.updateOrderStatus(orderId, 'Paid');
}
}
}
When you save both these files and restart the server, you get this error:
This error occurs because the NestJS Dependency Injection system container can not resolve the direct circular dependency between OrdersService
and PaymentService
.
When the app starts, both modules are instantiated and registered. Then NestJS tries to instantiate PaymentService
and sees that it needs an instance of OrdersService
to be injected as a dependency. NestJS checks for an instance of OrdersService
within the scope of the PaymentModule
. This causes circular dependency at the service level.
To resolve this, you can use the @Inject
decorator in combination with the forwardRef
function in the service constructors. This will tell NestJS to defer the resolution of the dependency the same way the forwardRef
works at the module level.
@Injectable()
export class OrdersService {
constructor(
@Inject(forwardRef(() => PaymentService))
private readonly paymentService: PaymentService,
) {}
@Injectable()
export class PaymentService {
constructor(
@Inject(forwardRef(() => OrdersService))
private readonly ordersService: OrdersService,
) {}
}
By using the @Inject(forwardRef(() => ServiceName))
, NestJS’s DI container defers the resolution of the specified service until it is needed. Now, both OrdersService
and PaymentService
can be instantiated without immediately needing the other to be fully resolved. This breaks the circular dependency at service level and allows NestJS to successfully inject the dependencies.
Suppose, in your E-commerce application, you want to have a refund process feature where users can request refunds of their orders if they are not satisfied. This feature will involve both Order and Payment modules.
OrderService
needs to verify an order’s eligibility for a refund (e.g. return timeframe or if it has been refunded already).PaymentService
needs to process the refund through any payment gateway you decide to integrate with and tell OrderService
to update the order status to refunded.This will create a circular dependency case as OrderService
depends on PaymentService
to process the refund and the PaymentService
depends on OrderService
to complete the refund process by updating the order refund status.
Instead of resolving the circular dependency that might occur with the forwardRef
function, you can decouple the services depending on each other by introducing a shared module called RefundManagementModule
. This module will orchestrate the refund process as follows:
import { Injectable } from '@nestjs/common';
import { OrderService } from './order.service';
import { PaymentService } from './payment.service';
@Injectable()
export class RefundManagementService {
constructor(
private orderService: OrderService,
private paymentService: PaymentService
) {}
async processRefund(orderId: string) {
const eligible = await this.orderService.checkRefundEligibility(orderId);
if (!eligible) {
throw new Error('Order not eligible for refund');
}
const refundSuccessful = await this.paymentService.processRefund(orderId);
if (refundSuccessful) {
await this.orderService.updateOrderStatus(orderId, 'Refunded');
}
}
}
Here, checkRefundEligibility
checks the refund eligibility for an order. If the eligibility criteria pass, it proceeds to process refund for that order using the processRefund
method from PaymentService
. This service can then be exported within its module RefundManagementModule
and imported in each of the OrdersService
and PaymentModule
for usage. With this, no two modules are depending on each other.
Tip: When writing your server applications, you should avoid circular dependency as much as possible. When you notice that you have a circular dependency, try to think of ways to refactor your application’s architecture and split up your code that does not require a lazy evaluator like the forwardRef
method we used earlier. If two services within a module context have a single method that they need, you can try to move it into a shared folder to have its own provider and utility-type module.
You can detect circular dependencies in your application by using a developer tool called Madge
. Madge
generates a visual graph of your module dependencies and finds circular dependencies in your code.
Install madge
as a dependency using npm i madge or yarn add madge
and run the command:
npx madge --circular src/main.ts
or
yarn madge --circular src/main.ts
The circular/.circular()
returns an array of all modules that have circular dependencies. To learn about other methods that can be called when using this package, visit here.
When you run the command, you should have an output like this in your terminal to show you where your circular files are:
The @Inject
decorator is a fundamental component of the NestJS’s DI system. Its primary function is explicitly defining a dependency to be injected into a class usually a provider.
In this article, you took a deep dive into understanding circular dependency in NestJS, the types, and best practices on how to resolve them. You will find the complete source code of this tutorial here on Github.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!