Fix: NestJS Circular Dependency — forwardRef and Module Design
Quick Answer
How to fix NestJS circular dependency errors — using forwardRef, restructuring module dependencies, extracting shared services, and understanding the NestJS module system.
The Error
NestJS throws a circular dependency error on startup:
[Nest] ERROR [ExceptionHandler] Nest cannot create the module instance.
Caught a "Circular dependency" error while processing provider "UserService".
Please, make sure that each side of a circular dependency has been decorated with an "forwardRef()" decorator.Or a less obvious dependency loop:
ERROR [ExceptionHandler] A circular dependency between modules has been detected.
Please, make sure that each side of a "forwardRef()" decorator is used.
The modules that form this cycle:
UsersModule -> PostsModule -> UsersModuleOr NestJS silently creates providers as undefined:
TypeError: Cannot read properties of undefined (reading 'findUser')
// userService is undefined because of unresolved circular depWhy This Happens
Circular dependencies occur when two or more providers or modules depend on each other, forming a cycle:
- Provider circular dependency —
UserServiceinjectsPostService, andPostServicealso injectsUserService. NestJS can’t instantiate either because each requires the other to exist first. - Module circular dependency —
UsersModuleimportsPostsModule, andPostsModuleimportsUsersModule. Same deadlock at the module level. - Indirect circular dependency —
A→B→C→A. The cycle may span three or more classes, making it harder to spot. - Missing
forwardRef()— even when the circular dep is intentional and unavoidable, both sides needforwardRef()so NestJS can resolve them lazily.
Fix 1: Use forwardRef for Provider Circular Dependencies
forwardRef() tells NestJS to use a lazy reference — the actual class is resolved after all providers are registered:
// WRONG — direct circular injection, NestJS can't resolve
// users.service.ts
@Injectable()
export class UserService {
constructor(private postService: PostService) {} // ← Circular
}
// posts.service.ts
@Injectable()
export class PostService {
constructor(private userService: UserService) {} // ← Circular
}// CORRECT — use forwardRef() on BOTH sides
// users.service.ts
import { forwardRef, Inject, Injectable } from '@nestjs/common';
import { PostService } from '../posts/post.service';
@Injectable()
export class UserService {
constructor(
@Inject(forwardRef(() => PostService))
private postService: PostService,
) {}
async getUserWithPosts(userId: number) {
const posts = await this.postService.findByAuthor(userId);
return { userId, posts };
}
}// posts.service.ts
import { forwardRef, Inject, Injectable } from '@nestjs/common';
import { UserService } from '../users/user.service';
@Injectable()
export class PostService {
constructor(
@Inject(forwardRef(() => UserService))
private userService: UserService,
) {}
async findByAuthor(userId: number) {
const user = await this.userService.findOne(userId);
return this.postRepository.find({ where: { authorId: user.id } });
}
}Common Mistake: Adding
forwardRef()only on one side. Both sides of the circular dependency must useforwardRef(). If only one side uses it, NestJS still can’t resolve the cycle.
Fix 2: Use forwardRef for Module Circular Dependencies
When two modules import each other, use forwardRef() on both module imports:
// WRONG — direct circular module import
// users.module.ts
@Module({
imports: [PostsModule], // ← Circular
providers: [UserService],
exports: [UserService],
})
export class UsersModule {}
// posts.module.ts
@Module({
imports: [UsersModule], // ← Circular
providers: [PostService],
exports: [PostService],
})
export class PostsModule {}// CORRECT — forwardRef() on both module imports
// users.module.ts
import { forwardRef, Module } from '@nestjs/common';
import { PostsModule } from '../posts/posts.module';
@Module({
imports: [forwardRef(() => PostsModule)], // Lazy module reference
providers: [UserService],
exports: [UserService],
})
export class UsersModule {}// posts.module.ts
import { forwardRef, Module } from '@nestjs/common';
import { UsersModule } from '../users/users.module';
@Module({
imports: [forwardRef(() => UsersModule)], // Lazy module reference
providers: [PostService],
exports: [PostService],
})
export class PostsModule {}Fix 3: Refactor to Break the Cycle (Preferred)
forwardRef() is a workaround. The better solution is redesigning the dependency structure to eliminate the cycle. Circular dependencies often indicate a violation of the Single Responsibility Principle — two modules are too tightly coupled.
Extract a shared service into a separate module:
Before (circular):
UsersModule ←→ PostsModule
After (no cycle):
UsersModule → SharedModule
PostsModule → SharedModule// shared/shared.module.ts — contains logic needed by both modules
@Module({
providers: [NotificationService, AuditService],
exports: [NotificationService, AuditService],
})
export class SharedModule {}
// users.module.ts — imports SharedModule, not PostsModule
@Module({
imports: [SharedModule],
providers: [UserService],
exports: [UserService],
})
export class UsersModule {}
// posts.module.ts — imports SharedModule, not UsersModule
@Module({
imports: [SharedModule],
providers: [PostService],
exports: [PostService],
})
export class PostsModule {}Move shared logic to a base service:
// Before — UserService calls PostService, PostService calls UserService
// After — both call a database service directly without importing each other
// database/database.service.ts
@Injectable()
export class DatabaseService {
async findUserById(id: number): Promise<User> {
return this.userRepository.findOne({ where: { id } });
}
async findPostsByAuthor(authorId: number): Promise<Post[]> {
return this.postRepository.find({ where: { authorId } });
}
}Use events to decouple services:
// Instead of PostService calling UserService directly,
// emit an event that UserService listens to
import { EventEmitter2 } from '@nestjs/event-emitter';
@Injectable()
export class PostService {
constructor(private eventEmitter: EventEmitter2) {}
async createPost(data: CreatePostDto) {
const post = await this.postRepository.save(data);
// Emit event instead of calling UserService directly
this.eventEmitter.emit('post.created', { postId: post.id, userId: data.authorId });
return post;
}
}
@Injectable()
export class UserService {
@OnEvent('post.created')
async handlePostCreated(event: { postId: number; userId: number }) {
await this.updateUserPostCount(event.userId);
}
}Fix 4: Detect Circular Dependencies Early
NestJS provides a built-in dependency graph visualization. Use it during development to spot cycles before they cause runtime errors:
# Install NestJS CLI
npm install -g @nestjs/cli
# Generate a dependency graph (outputs a JSON or DOT graph)
nest info
# For visual graph — use NestJS DevTools
npm install @nestjs/devtools-integration// main.ts — enable DevTools in development
import { NestFactory } from '@nestjs/core';
import { DevtoolsModule } from '@nestjs/devtools-integration';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
snapshot: true, // Required for DevTools
});
await app.listen(3000);
}
bootstrap();Simple manual cycle detection — trace the dependency chain:
UserService → PostService → CommentService → UserService ← cycle detectedDraw out your service dependencies. Any graph with a path from a node back to itself is a cycle.
Use eslint-plugin-import to detect circular imports at the TypeScript level:
npm install --save-dev eslint-plugin-import// .eslintrc.json
{
"plugins": ["import"],
"rules": {
"import/no-cycle": ["error", { "maxDepth": 3 }]
}
}Fix 5: Lazy-Load Services with ModuleRef
For cases where you only need a service in specific methods (not in the constructor), use ModuleRef to resolve it lazily:
import { Injectable, OnModuleInit } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { PostService } from '../posts/post.service';
@Injectable()
export class UserService implements OnModuleInit {
private postService: PostService;
constructor(private moduleRef: ModuleRef) {}
onModuleInit() {
// Resolve the service after all modules are initialized
this.postService = this.moduleRef.get(PostService, { strict: false });
}
async getUserWithPosts(userId: number) {
const posts = await this.postService.findByAuthor(userId);
return { userId, posts };
}
}ModuleRef.get() with strict: false searches the entire application context, not just the current module scope. Without this flag, it only looks in the current module.
For request-scoped providers, use resolve() instead of get():
async someMethod() {
const postService = await this.moduleRef.resolve(PostService);
return postService.findAll();
}Fix 6: Structure Modules to Avoid Cycles
A module hierarchy that naturally avoids cycles:
AppModule
├── CoreModule (no deps on feature modules)
│ ├── DatabaseModule
│ ├── LoggerModule
│ └── ConfigModule
├── SharedModule (depends on CoreModule only)
│ ├── EmailService
│ ├── NotificationService
│ └── AuditService
├── UsersModule (depends on Core + Shared)
│ └── UserService
└── PostsModule (depends on Core + Shared + Users)
└── PostServiceRules that prevent circular dependencies:
- Core modules don’t import feature modules.
- Shared modules only import core modules.
- Feature modules import core and shared — never each other directly.
- If two feature modules need to communicate, use events (
EventEmitter2) or extract the shared logic intoSharedModule.
Barrel exports from module boundaries:
// users/index.ts — public API of the UsersModule
export { UserService } from './user.service';
export { User } from './entities/user.entity';
export { CreateUserDto } from './dto/create-user.dto';
// Other modules import from the barrel, not internal files
// This makes dependencies explicit and easier to audit
import { UserService } from '../users'; // Clean boundaryStill Not Working?
Indirect cycles are harder to spot. If A → B → C → A, adding forwardRef() to only A and C won’t help — you need to trace the full cycle.
Check for accidental self-injection — a service that injects itself:
@Injectable()
export class UserService {
constructor(
private userService: UserService, // ← Self-injection: always a circular dep
) {}
}forwardRef() doesn’t work with all provider types. It works with class providers but may behave unexpectedly with factory providers. Prefer extracting shared logic over using forwardRef() with factories.
Verify the exported providers in each module. A circular import is harmless if neither module actually injects from the other — but the error appears if the import creates a circular module reference even without injecting providers. Use exports arrays to make only necessary providers available:
@Module({
providers: [UserService, UserRepository], // UserRepository is internal
exports: [UserService], // Only UserService is exported
})
export class UsersModule {}For related NestJS issues, see Fix: NestJS TypeORM QueryFailedError and Fix: Spring Boot Circular Dependency Error.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: NestJS Swagger UI Not Showing — /api-docs Returns 404 or Blank Page
How to fix NestJS Swagger UI not displaying — SwaggerModule setup, DocumentBuilder, decorators not appearing, guards blocking the docs route, and Fastify vs Express differences.
Fix: NestJS Nest can't resolve dependencies — Provider Not Found Error
How to fix NestJS dependency injection errors — module imports, provider exports, circular dependencies, dynamic modules, and the most common 'can't resolve dependencies' patterns.
Fix: NestJS ValidationPipe Not Working — class-validator Decorators Ignored
How to fix NestJS ValidationPipe not validating requests — global pipe setup, class-transformer, whitelist and transform options, custom validators, and DTO inheritance issues.
Fix: NestJS Guard Not Working — canActivate Always Passes or Is Never Called
How to fix NestJS guards not working — applying guards globally vs controller vs method level, JWT AuthGuard, metadata with Reflector, public routes, and guard execution order.