Breaking Circular Dependencies
Diagnose the 'undefined dependency' error and resolve two-way references with forwardRef — or better, by removing the cycle.
Sooner or later two services need each other: UsersService calls AuthService and AuthService calls UsersService. Nest builds its container by resolving one provider before the other, and a cycle means neither can go first. You get a cryptic "Nest can't resolve dependencies" or an undefined injected value.
The escape hatch: forwardRef
forwardRef() tells Nest "this reference will exist later, wire it up lazily." You wrap the type on both sides of the cycle - at the injection point and, for module-to-module cycles, in the imports array.
@Injectable()
export class UsersService {
constructor(
@Inject(forwardRef(() => AuthService))
private readonly auth: AuthService,
) {}
}Module-level cycles
If the two providers live in different modules, each module must forwardRef the other in its imports, and each must export the provider the other needs. Miss one half and the injection silently resolves to undefined.
The senior instinct: don't have the cycle
forwardRef works, but a cycle is usually a design smell. Three cleaner fixes, in order of preference:
- Extract the shared logic into a third provider both sides depend on. The cycle disappears.
- Invert with an event - instead of calling back, emit a domain event the other side listens for.
- Use a lazy accessor via
ModuleRefto resolve the dependency on first use rather than at construction.
Reach for forwardRef to unblock yourself today, then schedule the refactor.
Example
import {
Injectable,
Inject,
forwardRef,
Module,
} from '@nestjs/common';
@Injectable()
export class UsersService {
constructor(
@Inject(forwardRef(() => AuthService))
private readonly auth: AuthService,
) {}
findById(id: string) {
return { id, name: 'Ada' };
}
isCurrentUser(id: string) {
return this.auth.currentUserId() === id;
}
}
@Injectable()
export class AuthService {
private activeId = 'u1';
constructor(
@Inject(forwardRef(() => UsersService))
private readonly users: UsersService,
) {}
currentUserId() {
return this.activeId;
}
whoAmI() {
return this.users.findById(this.activeId);
}
}
// Both modules forwardRef each other:
@Module({
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
@Module({
imports: [forwardRef(() => UsersModule)],
providers: [AuthService],
exports: [AuthService],
})
export class AuthModule {}
// Cleaner alternative — no cycle at all:
@Injectable()
export class SessionService {
private activeId = 'u1';
currentUserId() {
return this.activeId;
}
}
// Now UsersService and AuthService both depend on SessionService,
// and neither depends on the other.When to use it
- A developer gets a "Nest can't resolve dependencies" error because UsersService imports AuthService and AuthService imports UsersService, creating a circular reference that forwardRef resolves.
- A team refactors two mutually dependent modules by extracting shared logic into a third CommonModule, eliminating the circular import entirely.
- An engineer uses forwardRef on the module @Module imports array, not just the constructor, when two feature modules import each other.
More examples
Circular dependency error
Illustrates the circular dependency pattern where two services import each other, causing Nest's DI container to fail at resolution time.
// ❌ Circular: AuthService ↔ UsersService
@Injectable()
export class AuthService {
constructor(private usersService: UsersService) {} // imports users
}
@Injectable()
export class UsersService {
constructor(private authService: AuthService) {} // imports auth
// → Nest throws: 'Nest can\'t resolve dependencies of UsersService'Resolve with forwardRef
Breaks the deadlock by wrapping both injection tokens in forwardRef so the DI container can defer resolution until both classes are defined.
import { Injectable, forwardRef, Inject } from '@nestjs/common';
@Injectable()
export class AuthService {
constructor(
@Inject(forwardRef(() => UsersService))
private usersService: UsersService,
) {}
}
@Injectable()
export class UsersService {
constructor(
@Inject(forwardRef(() => AuthService))
private authService: AuthService,
) {}
}forwardRef on module imports
Shows that forwardRef must be applied to the module imports array as well as the service constructors for Nest to resolve the cycle.
import { Module, forwardRef } from '@nestjs/common';
@Module({
imports: [forwardRef(() => AuthModule)], // ← also needed on module level
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
@Module({
imports: [forwardRef(() => UsersModule)],
providers: [AuthService],
exports: [AuthService],
})
export class AuthModule {}
Discussion