Provider (프로바이더)
Provider는 NestJS에서 의존성 주입(DI)의 핵심 단위로 Nest의 DI(의존성 주입) 대상 객체(서비스, 리포지토리, 팩토리, 상수 등)를 말합니다.
모듈의 providers 배열에 등록하면 Nest의 DI 컨테이너가 이를 관리합니다.
즉, Provider는 “주입 가능한 객체(Injectable Object)”입니다.
Class Provider (기본)
@Injectable()로 선언한 클래스를 그대로 providers에 넣는 방식입니다.
가장 흔하게 쓰이는 형태 — 서비스, 리포지토리 등
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// users.service.ts
import { Injectable } from "@nestjs/common";
@Injectable()
export class UsersService {
findAll() {
return ["u1", "u2"];
}
}
// users.module.ts
@Module({
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
Value Provider (useValue)
단순 값(설정, 상수 등)을 주입할 때 사용합니다.
동작은 항상 동일한 값을 그대로 반환합니다
1
2
3
4
5
6
7
8
9
10
11
// app.module.ts
@Module({
providers: [{ provide: "APP_NAME", useValue: "MovieStory" }],
})
export class AppModule {}
// 어떤 서비스에서 사용
@Injectable()
export class SomeService {
constructor(@Inject("APP_NAME") private appName: string) {}
}
Scope
NestJS Provider의 Scope는 Provider의 수명 주기(lifecycle)를 제어하는 기능입니다.
“Provider 인스턴스가 언제 새로 만들어지고 언제 재사용되는가”를 결정
| Scope | 설명 | 인스턴스 생성 시점 | 주 사용 예시 |
|---|---|---|---|
| Singleton | 기본값 (앱 전체에서 한 번만 생성) | 앱 시작 시 1회 | 대부분의 서비스, Repository |
| Request | 요청마다 새 인스턴스 생성 | HTTP 요청마다 | 요청 단위 데이터 추적, 사용자 컨텍스트 |
| Transient | 주입될 때마다 새 인스턴스 생성 | 매번 의존성 주입 시 | 독립적인 임시 객체, 멀티 인스턴스 필요 시 |
Singleton Scope (기본값)
앱 전체에서 한 번만 생성
- 앱 종료 시까지 유지
- 빠르고 효율적
- 상태를 유지하려면 주의 (공유 데이터 오염 가능)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Injectable()
export class UserService {
private count = 0;
getCount() {
return ++this.count;
}
}
@Controller("test")
export class TestController {
constructor(private readonly userService: UserService) {}
@Get()
test() {
return this.userService.getCount();
}
}
여러 요청을 보내도 같은 인스턴스를 재사용하므로 count가 계속 증가
Request Scope
요청(Request) 단위로 Provider 인스턴스를 새로 생성
요청마다 격리된 인스턴스 (안전한 요청 단위 상태 저장 가능)
Nest 내부적으로 Request Context DI 컨테이너를 별도로 생성
성능 비용이 있음 (요청마다 새 인스턴스 생성)
Controller 또는 다른 Request-Scoped Provider에서만 주입 가능
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { Injectable, Scope } from "@nestjs/common";
@Injectable({ scope: Scope.REQUEST })
export class RequestScopedService {
private id = Math.random();
getId() {
return this.id;
}
}
@Controller("request")
export class RequestController {
constructor(private readonly reqService: RequestScopedService) {}
@Get()
getId() {
return this.reqService.getId();
}
}
매 요청마다 새 인스턴스가 만들어지므로 id 값이 달라짐
Transient Scope
주입될 때마다 새로운 인스턴스를 생성
- 매번 새 객체가 필요할 때 사용
- 요청(Request)보다 더 짧은 생명주기
- 상태 공유를 완전히 피하고 싶은 경우
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { Injectable, Scope } from "@nestjs/common";
@Injectable({ scope: Scope.TRANSIENT })
export class TransientService {
private id = Math.random();
getId() {
return this.id;
}
}
@Controller("transient")
export class TransientController {
constructor(
private readonly t1: TransientService,
private readonly t2: TransientService
) {}
@Get()
compare() {
return {
t1: this.t1.getId(),
t2: this.t2.getId(),
};
}
}
같은 요청 안에서도 t1 ≠ t2, 즉 항상 새로운 인스턴스
Lifecycle Hook
Nest는 여러 라이프사이클 포인트를 제공해 초기화 / 부트스트랩 / 종료 시점에 로직을 실행할 수 있습니다.
수명주기 메서드는 클래스가 해당 인터페이스를 구현하면 Nest가 자동으로 호출합니다.
Hook의 전체 흐름
Nest 앱이 실행되면 아래 순서로 훅이 동작합니다.
1
2
3
4
5
6
7
8
9
10
Application Start
│
├─ Module 초기화
│ └─ OnModuleInit
│
├─ Provider 초기화
│ └─ OnApplicationBootstrap
│
└─ 서버 구동 완료 (ready)
└─ OnModuleDestroy / OnApplicationShutdown (종료 시)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Injectable()
export class ExampleService
implements
OnModuleInit,
OnApplicationBootstrap,
BeforeApplicationShutdown,
OnApplicationShutdown
{
onModuleInit() {
console.log("[1] 모듈 초기화 완료");
}
onApplicationBootstrap() {
console.log("[2] 앱 전체 부팅 완료");
}
beforeApplicationShutdown(signal?: string) {
console.log("[3] 종료 직전 준비 단계", signal);
}
onApplicationShutdown(signal?: string) {
console.log("[4] 종료 시 정리 단계", signal);
}
}
1
2
3
4
5
6
// 콘솔 출력
[1] 모듈 초기화 완료
[2] 앱 전체 부팅 완료
Ctrl + C (SIGINT)
[3] 종료 직전 준비 단계 SIGINT
[4] 종료 시 정리 단계 SIGINT
즉, 앱의 시작 단계와 종료 단계 각각에서 실행되는 훅이 존재합니다.
OnModuleInit
모듈 내 Provider가 모두 생성되고, 의존성 주입이 끝난 후 실행
외부 API 연결, 초기 데이터 로드 등 “서비스 초기화 로직”에 적합
1
2
3
4
5
6
7
8
import { Injectable, OnModuleInit } from "@nestjs/common";
@Injectable()
export class UserService implements OnModuleInit {
onModuleInit() {
console.log("UserService 초기화 완료");
}
}
OnApplicationBootstrap
애플리케이션이 완전히 구동되고 모든 모듈 초기화가 끝난 뒤 실행
모든 모듈의 onModuleInit()이 끝난 후 실행
전역 설정, 캐시 warming, 스케줄러 시작 등에 적합
1
2
3
4
5
6
@Injectable()
export class AppService implements OnApplicationBootstrap {
onApplicationBootstrap() {
console.log("✅ 모든 모듈 초기화 완료 후 실행");
}
}
OnApplicationShutdown
애플리케이션이 종료될 때 호출
서버가 종료되거나 SIGINT, SIGTERM 시그널을 받을 때 실행
DB 커넥션, 외부 소켓, 큐, 로그 등 자원 정리에 사용
1
2
3
4
5
6
7
@Injectable()
export class DbService implements OnApplicationShutdown {
async onApplicationShutdown(signal?: string) {
console.log("🛑 앱 종료 감지:", signal);
await this.closeConnection();
}
}
예시
1 2 3 4 5 6 7 8 9 10 11
@Injectable() export class DbService implements OnModuleInit, OnModuleDestroy { private client: DbClient; async onModuleInit() { this.client = await createDbClient(); await this.client.connect(); } async onModuleDestroy() { await this.client.close(); } }