Circular Dependencies

Break the Loop With forwardRef

Circular Dependencies

Two classes that need each other can't both be injected normally. Nest ships an escape hatch — forwardRef — for when refactoring isn't an option.

4 min read Level 3/5 #nestjs#di#advanced
What you'll learn
  • Identify a circular-dependency error in startup logs
  • Use forwardRef in both directions between providers
  • Prefer refactoring to a shared third module when possible

If A needs B in its constructor and B needs A in its constructor, neither can be built first. Nest detects this and errors at startup. The official workaround is forwardRef.

What the Error Looks Like

You’ll see something like:

Nest can't resolve dependencies of the UsersService (?).
Please make sure that the argument dependency at index [0] is
available in the UsersModule context.

When the dependency at the question mark would resolve to a class that imports back to you, you’ve got a cycle.

Fix With forwardRef

Wrap both sides with forwardRef. The wrapper defers reading the reference until it’s needed, so the import cycle stops mattering.

// users.service.ts
@Injectable()
export class UsersService {
  constructor(
    @Inject(forwardRef(() => OrdersService))
    private readonly orders: OrdersService,
  ) {}
}

// orders.service.ts
@Injectable()
export class OrdersService {
  constructor(
    @Inject(forwardRef(() => UsersService))
    private readonly users: UsersService,
  ) {}
}

Modules Cycle Too

The same trick works at the module level when two feature modules import each other.

@Module({
  imports: [forwardRef(() => OrdersModule)],
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}

@Module({
  imports: [forwardRef(() => UsersModule)],
  providers: [OrdersService],
  exports: [OrdersService],
})
export class OrdersModule {}

Prefer a Refactor

forwardRef works, but a cycle is usually a design smell. Three common fixes that get rid of it instead:

  1. Extract a shared module. If UsersService and OrdersService both need BillingHelpers, pull that helper into a separate module both import.
  2. Push events through a bus. Replace users.notifyOrders() with eventEmitter.emit('user.created', ...) and let orders listen. Now the dependency only goes one way.
  3. Invert the relationship. Often only one side really needs the other; the back-edge was added “just in case.”
// Better: one-way + an event
@Injectable()
export class UsersService {
  constructor(private readonly events: EventEmitter2) {}

  async create(dto: CreateUserDto) {
    const user = await this.repo.save(dto);
    this.events.emit('user.created', user); // orders can listen
    return user;
  }
}

Use forwardRef when you must — and treat each instance as a TODO to revisit when there’s time.

Dynamic Modules →