Home Provider, Lifecycle Hook, Middleware
Post
X

Provider, Lifecycle Hook, Middleware

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();
      }
    }
    
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.