NestJS에서 비즈니스 로직을 담당하는 대부분의 클래스는 Provider로 등록된다. Provider는 NestJS의 IoC 컨테이너가 생성과 소멸을 책임지며, 필요한 곳에 자동으로 주입해주는 객체다. @Injectable 데코레이터 하나로 클래스가 DI 대상이 된다.
Provider는 @Injectable 데코레이터가 붙은 클래스 또는 값·팩토리로 정의한 객체를 말한다. Module의 providers 배열에 등록하면 NestJS IoC 컨테이너가 인스턴스를 관리한다.
Module
└─ providers: [CatsService, DogsService]
│
▼
IoC Container
┌────────────────────────────┐
│ token → instance (cache) │
│ CatsService → <obj> │
│ DogsService → <obj> │
└────────────────────────────┘
│
▼ constructor injection
CatsController
IoC(Inversion of Control): 객체 생성 제어권을 개발자가 아닌 프레임워크가 가져가는 패턴.
NestJS는 세 가지 Provider 등록 문법을 지원한다.
| 방식 | 용도 | 예시 |
|---|---|---|
| Class Provider | 일반 서비스 클래스 | useClass: CatsService |
| Value Provider | 상수·설정 객체 주입 | useValue: { apiKey: '...' } |
| Factory Provider | 런타임에 동적 생성 | useFactory: () => new Service |
Class Provider — 가장 흔한 방식으로, 단축 문법과 명시적 문법이 동일하게 동작한다.
// 단축 문법
@Module({ providers: [CatsService] })
// 명시적 문법 (동일한 의미)
@Module({
providers: [{ provide: CatsService, useClass: CatsService }],
})
Value Provider — 외부 설정이나 mock 객체를 토큰으로 바인딩할 때 사용한다.
const mockCatsService = { findAll: () => [] };
@Module({
providers: [{ provide: CatsService, useValue: mockCatsService }],
})
Factory Provider — 비동기 초기화나 조건부 생성이 필요한 경우에 쓴다.
@Module({
providers: [
{
provide: 'DB_CONNECTION',
useFactory: async (config: ConfigService) => {
return createConnection(config.get('DB_URL'));
},
inject: [ConfigService],
},
],
})
inject 배열에 나열한 토큰이 useFactory 함수의 인자로 순서대로 전달된다.
클래스가 아닌 문자열·Symbol 토큰을 주입받을 때는 @Inject 데코레이터를 명시해야 한다.
@Injectable()
export class CatsService {
constructor(
@Inject('DB_CONNECTION') private readonly db: Connection,
) {}
}
클래스 타입 토큰은 TypeScript 리플렉션으로 자동 해석되므로 @Inject가 불필요하다. 문자열·Symbol 토큰은 런타임에 타입 정보가 사라지기 때문에 반드시 명시해야 한다.
NestJS Provider의 기본 스코프는 Singleton이다. 같은 Module 내에서 여러 클래스가 동일한 Provider를 주입받아도 컨테이너는 인스턴스를 한 번만 생성하고 캐싱해 재사용한다.
Request A ──┐
├──▶ CatsService (동일 인스턴스)
Request B ──┘
요청마다 새 인스턴스가 필요하면 @Injectable({ scope: Scope.REQUEST })로 스코프를 변경할 수 있지만, Singleton 대비 성능 비용이 발생한다.
다른 Module에서 Provider를 사용하려면 exports에 추가해야 한다.
@Module({
providers: [CatsService],
exports: [CatsService], // 외부 Module에서 주입 가능
})
export class CatsModule {}
exports에 없는 Provider는 Module 경계 밖에서 접근할 수 없다. 이 캡슐화 덕분에 의존성 그래프가 명시적으로 유지된다.