NestJS 애플리케이션은 Module 단위로 구성된다. 각 Module은 연관된 Controller, Provider, Service를 하나의 경계 안에 묶어 관심사를 분리한다. 이 경계 덕분에 애플리케이션이 커져도 의존성 방향이 명확하게 유지된다.
Module은 @Module 데코레이터 하나로 정의된다. 데코레이터에 전달하는 메타데이터 객체가 해당 모듈의 역할을 결정한다.
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
@Module({
imports: [],
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
imports — 이 모듈에서 사용할 다른 모듈을 등록한다. controllers — HTTP 요청을 처리하는 Controller 목록. providers — DI 컨테이너에 등록할 Service·Repository 등. exports — 다른 모듈이 주입받을 수 있도록 공개할 Provider 목록.
| 종류 | 설명 |
|---|---|
| Root Module | AppModule. 애플리케이션 진입점, 모든 모듈의 루트 |
| Feature Module | 도메인별로 분리한 모듈 (UsersModule, OrdersModule 등) |
| Shared Module | 여러 Feature Module이 공통으로 사용하는 Provider를 exports로 공개 |
| Global Module | @Global 데코레이터로 모든 모듈에 자동 주입 |
| Dynamic Module | forRoot / forFeature 패턴으로 런타임에 설정을 주입 |
NestJS는 부트스트랩 시점에 모든 @Module 메타데이터를 분석해 의존성 그래프를 구성한다. 그래프가 순환 참조 없이 DAG(Directed Acyclic Graph) 형태여야 정상 부팅된다.
AppModule
├── UsersModule
│ └── exports: [UsersService]
├── OrdersModule
│ └── imports: [UsersModule] ← UsersService 주입 가능
└── DatabaseModule (Global)
└── @Global() → 모든 모듈에서 자동 사용 가능
같은 Provider를 여러 모듈에서 쓰려면 해당 Provider를 보유한 모듈을 Shared Module로 만들고 exports에 올린다. imports 없이 Provider를 직접 복사·붙여넣기하면 인스턴스가 여러 개 생겨 상태 불일치가 발생한다.
// shared/database.module.ts
@Module({
providers: [DatabaseService],
exports: [DatabaseService], // 공개
})
export class DatabaseModule {}
// users/users.module.ts
@Module({
imports: [DatabaseModule], // 가져오기
providers: [UsersService],
})
export class UsersModule {}
NestJS의 Module은 싱글턴 스코프가 기본이다. 같은 모듈을 여러 곳에서 imports해도 Provider 인스턴스는 하나만 생성된다.
설정값을 런타임에 주입해야 할 때 Dynamic Module 패턴을 사용한다. TypeOrmModule.forRoot나 ConfigModule.forRoot가 대표적인 예다.
@Module({})
export class LoggerModule {
static forRoot(options: LoggerOptions): DynamicModule {
return {
module: LoggerModule,
providers: [
{ provide: LOGGER_OPTIONS, useValue: options },
LoggerService,
],
exports: [LoggerService],
global: options.global ?? false,
};
}
}
forRoot는 앱 전역 설정에, forFeature는 특정 Feature Module 범위의 설정에 쓴다. 이 패턴 덕분에 라이브러리 수준의 모듈도 사용자 설정을 유연하게 받을 수 있다.