Skip to content

NestJS 커스텀 프로바이더

의존성 주입(Dependency Injection) 기초

NestJS는 의존성 주입(DI)을 핵심적으로 내장하고 있으며, 이는 주로 서비스 제공자 인스턴스를 클래스에 주입하는 생성자 기반 의존성 주입을 통해 이루어집니다. @Injectable() 데코레이터를 사용하여 클래스를 프로바이더로 선언하고, 모듈에 등록함으로써 Nest IoC(Inversion of Control) 컨테이너에서 관리할 수 있게 합니다.

예제 코드:

typescript
// cats.service.ts
import { Injectable } from "@nestjs/common";
import { Cat } from "./interfaces/cat.interface";

@Injectable()
export class CatsService {
  private readonly cats: Cat[] = [];

  findAll(): Cat[] {
    return this.cats;
  }
}
typescript
// cats.controller.ts
import { Controller, Get } from "@nestjs/common";
import { CatsService } from "./cats.service";
import { Cat } from "./interfaces/cat.interface";

@Controller("cats")
export class CatsController {
  constructor(private catsService: CatsService) {}

  @Get()
  async findAll(): Promise<Cat[]> {
    return this.catsService.findAll();
  }
}
typescript
// app.module.ts
import { Module } from "@nestjs/common";
import { CatsController } from "./cats/cats.controller";
import { CatsService } from "./cats/cats.service";

@Module({
  controllers: [CatsController],
  providers: [
    CatsService
    // 아래 코드와 동일
    {
      provide: CatsService,
      useClass: CatsService,
    },
  ],
})
export class AppModule {}

커스텀 프로바이더

애플리케이션의 복잡도가 증가함에 따라 표준 프로바이더만으로는 충분하지 않을 수 있습니다. Nest는 이를 위해 커스텀 프로바이더를 정의할 수 있는 여러 방법을 제공합니다. 커스텀 프로바이더는 다음과 같은 경우에 유용합니다:

  • Nest가 클래스의 인스턴스를 생성하는 대신 사용자 정의 인스턴스를 생성하고 싶을 때
  • 기존 클래스를 두 번째 의존성으로 재사용하고 싶을 때
  • 테스트를 위해 클래스를 모의(mock) 버전으로 대체하고 싶을 때

Value providers: useValue

  • useValue는 상수 값, 외부 라이브러리, 또는 실제 구현을 모의 객체로 대체할 때 유용합니다.
  • 테스트 목적으로 CatsService의 모의 버전을 사용하도록 Nest에 지시할 수 있습니다.
typescript
// AppModule에서 useValue 예제
@Module({
  providers: [
    {
      provide: CatsService,
      useValue: mockCatsService,
    },
  ],
})
export class AppModule {}

비클래스 기반 토큰 (Non-class-based provider tokens)

  • 클래스 이름 대신 문자열이나 심볼을 DI 토큰으로 사용할 수 있습니다.
  • 예를 들어, 외부 파일에서 가져온 연결 객체에 문자열 값을 토큰으로 사용할 수 있습니다.
typescript
@Module({
  providers: [
    {
      provide: "CONNECTION",
      useValue: connection,
    },
  ],
})
export class AppModule {}

@Injectable()
export class CatsRepository {
  constructor(@Inject("CONNECTION") connection: Connection) {}
}

Class providers: useClass

  • useClass는 토큰이 해결해야 하는 클래스를 동적으로 결정할 수 있게 합니다.
  • 현재 환경에 따라 다른 구성 서비스 구현을 제공하도록 할 수 있습니다.
typescript
// configServiceProvider 예제
const configServiceProvider = {
  provide: ConfigService,
  useClass:
    process.env.NODE_ENV === "development"
      ? DevelopmentConfigService
      : ProductionConfigService,
};

Factory providers: useFactory

  • useFactory는 팩토리 함수에서 반환된 값으로 프로바이더를 동적으로 생성할 수 있게 합니다.
  • 팩토리 함수는 필요한 다른 프로바이더를 주입받을 수 있으며, 이들은 선택적일 수도 있습니다.
typescript
// connectionProvider 예제
const connectionProvider = {
  provide: "CONNECTION",
  useFactory: (optionsProvider: OptionsProvider) =>
    new DatabaseConnection(optionsProvider.get()),
  inject: [OptionsProvider],
};

Alias providers: useExisting

  • useExisting을 사용하면 기존 프로바이더에 대한 별칭을 생성할 수 있습니다.
  • 이를 통해 같은 프로바이더를 다른 두 가지 방법으로 접근할 수 있습니다.
typescript
@Injectable()
class LoggerService {
  /* implementation details */
}

const loggerAliasProvider = {
  provide: "AliasedLoggerService",
  useExisting: LoggerService,
};

@Module({
  providers: [LoggerService, loggerAliasProvider],
})
export class AppModule {}

비서비스 기반 프로바이더 (Non-service based providers)

  • 프로바이더는 서비스를 제공하는 것에만 한정되지 않습니다. 예를 들어, 현재 환경에 따라 구성 객체 배열을 제공할 수 있습니다.
typescript
const configFactory = {
  provide: "CONFIG",
  useFactory: () => {
    return process.env.NODE_ENV === "development" ? devConfig : prodConfig;
  },
};

@Module({
  providers: [configFactory],
})
export class AppModule {}

커스텀 프로바이더 내보내기 (Export custom provider)

  • 커스텀 프로바이더는 선언된 모듈에 한정되어 있습니다. 다른 모듈에서도 사용하려면 내보내야 합니다.
  • 커스텀 프로바이더를 내보낼 때는 토큰 또는 전체 프로바이더 객체를 사용할 수 있습니다.
typescript
// 토큰 사용하여 내보내기 예제
@Module({
  providers: [connectionFactory],
  exports: ["CONNECTION"],
})
export class AppModule {}
typescript
// 전체 프로바이더 객체를 사용하여 내보내기 예제
@Module({
  providers: [connectionFactory],
  exports: [connectionFactory],
})
export class AppModule {}