Skip to content
NestJS ns providers 4 min read

Circular Dependencies

A circular dependency occurs when two providers (or two modules) reference each other, directly or through a chain. Because Nest builds its dependency graph eagerly at startup, neither side can be constructed first, and the container fails to resolve the cycle. Nest gives you forwardRef() to break the deadlock and ModuleRef to resolve dependencies lazily, but the cleaner long-term answer is almost always to refactor the cycle away.

How a cycle manifests

When UsersService depends on AuthService and AuthService depends back on UsersService, Nest cannot decide which to instantiate first. At boot you get a resolution error that points at an undefined dependency, because by the time Nest reads the metadata for one class, the other is not yet defined.

// auth/auth.service.ts
import { Injectable } from '@nestjs/common';
import { UsersService } from '../users/users.service';

@Injectable()
export class AuthService {
  constructor(private readonly usersService: UsersService) {}

  validate(id: string) {
    return this.usersService.findById(id);
  }
}
// users/users.service.ts
import { Injectable } from '@nestjs/common';
import { AuthService } from '../auth/auth.service';

@Injectable()
export class UsersService {
  constructor(private readonly authService: AuthService) {}

  findById(id: string) {
    return { id, role: this.authService.roleFor(id) };
  }
}

Output:

[Nest] ERROR [ExceptionHandler] Nest can't resolve dependencies of the AuthService (?).
Please make sure that the argument UsersService at index [0] is available in the AuthModule context.
Potential causes:
- A circular dependency between modules. Use forwardRef() to avoid it.

Breaking a provider cycle with forwardRef

When the cycle is between two providers, wrap the type reference on both sides with forwardRef() and pair it with the @Inject() decorator. forwardRef defers reading the referenced token until both classes exist, so Nest can instantiate one with a partially-resolved reference and patch it once the other is ready.

// auth/auth.service.ts
import { Injectable, Inject, forwardRef } from '@nestjs/common';
import { UsersService } from '../users/users.service';

@Injectable()
export class AuthService {
  constructor(
    @Inject(forwardRef(() => UsersService))
    private readonly usersService: UsersService,
  ) {}

  roleFor(id: string) {
    return this.usersService.findById(id) ? 'member' : 'guest';
  }
}
// users/users.service.ts
import { Injectable, Inject, forwardRef } from '@nestjs/common';
import { AuthService } from '../auth/auth.service';

@Injectable()
export class UsersService {
  constructor(
    @Inject(forwardRef(() => AuthService))
    private readonly authService: AuthService,
  ) {}

  findById(id: string) {
    return { id };
  }
}

Both sides of the cycle must use forwardRef(). Wrapping only one provider still leaves the other unable to resolve its dependency, and the same boot-time error returns.

Breaking a module cycle

If the two providers live in different modules and each module exports its service to the other, the modules themselves form a cycle. Apply forwardRef() in the imports array of each module rather than (or in addition to) the provider level.

// auth/auth.module.ts
import { Module, forwardRef } from '@nestjs/common';
import { UsersModule } from '../users/users.module';
import { AuthService } from './auth.service';

@Module({
  imports: [forwardRef(() => UsersModule)],
  providers: [AuthService],
  exports: [AuthService],
})
export class AuthModule {}
// users/users.module.ts
import { Module, forwardRef } from '@nestjs/common';
import { AuthModule } from '../auth/auth.module';
import { UsersService } from './users.service';

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

Resolving lazily with ModuleRef

ModuleRef is an alternative that avoids declaring the dependency in the constructor at all. You inject the container itself and fetch the provider on demand, which sidesteps the construction-order problem because resolution happens after the graph is fully built.

import { Injectable, OnModuleInit } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { UsersService } from '../users/users.service';

@Injectable()
export class AuthService implements OnModuleInit {
  private usersService!: UsersService;

  constructor(private readonly moduleRef: ModuleRef) {}

  onModuleInit() {
    this.usersService = this.moduleRef.get(UsersService, { strict: false });
  }

  roleFor(id: string) {
    return this.usersService.findById(id) ? 'member' : 'guest';
  }
}

The { strict: false } option tells Nest to search the whole application for the token rather than only the current module. For request-scoped or transient providers, use the async moduleRef.resolve(Token) instead, since each call returns a distinct instance.

Choosing between the approaches

ApproachWhen to useTrade-off
forwardRef() (providers)A genuine two-way relationship between two servicesBoth sides must be wrapped; clutters constructors
forwardRef() (modules)The cycle is between module boundariesHides architectural coupling
ModuleRefYou need a dependency only occasionally, or for dynamic resolutionLoses compile-time DI guarantees
RefactorThe cycle signals a design smellRequires restructuring code

Refactoring to remove the cycle

forwardRef() works, but a circular dependency usually means two classes share a responsibility that belongs somewhere else. Extracting that shared logic into a third provider breaks the loop cleanly and improves cohesion.

// shared/role.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class RoleService {
  roleFor(id: string): 'member' | 'guest' {
    return id ? 'member' : 'guest';
  }
}

Now both AuthService and UsersService depend on RoleService (and on each other no longer), turning the cycle into a simple tree. Place RoleService in a shared module that both feature modules import.

Best practices

  • Treat a forwardRef() requirement as a signal to revisit your module boundaries before reaching for the workaround.
  • Always wrap both ends of a provider cycle; a one-sided forwardRef() does not resolve.
  • Prefer extracting shared behaviour into a third provider over keeping a permanent cycle.
  • Use ModuleRef.get() for singletons and ModuleRef.resolve() for request-scoped or transient providers.
  • Keep the lazy ModuleRef lookup in a lifecycle hook like onModuleInit so the provider is guaranteed to exist.
  • Read the boot-time resolution error carefully — Nest names the failing class and the index of the unresolved argument.
Last updated June 14, 2026
Was this helpful?