NestJS Providers와 의존성 주입(DI)
NestJS Providers와 의존성 주입(DI)
개요
컨트롤러는 HTTP 요청을 받는 일만 하고, 실제 비즈니스 로직은 밖으로 빼는 게 NestJS의 기본 사고방식이에요. 그 로직을 담당하는 서비스, 저장소, 팩토리, 헬퍼 같은 것들을 통틀어 provider라고 부릅니다. provider의 핵심 성질은 의존성으로 주입될 수 있다는 것인데, 이 덕분에 객체들이 서로 관계를 맺으면서도 구현을 갈아 끼우기 쉬워져요. 컨트롤러가 HTTP를 처리하고 provider에 복잡한 일을 위임하는 구조죠.
핵심 개념
@Injectable로 IoC 컨테이너에 등록
provider는 기본적으로 @Injectable() 데코레이터를 단 클래스예요. 이 데코레이터가 클래스에 메타데이터를 붙여서, Nest의 IoC 컨테이너가 이 클래스를 관리할 수 있음을 알려줍니다. 컨테이너는 앱 시작 시 의존성을 해석해 provider를 생성하고, 앱이 끝나면 함께 정리해요. provider의 생성 시점을 **스코프(scope)**라고 하는데, 기본적으로 앱 생명주기와 맞춰집니다.
생성자 주입으로 해석
주입은 생성자에서 이뤄져요.
@Controller('cats')
export class CatsController {
constructor(private catsService: CatsService) {}
}
여기서 TypeScript의 타입 주석 CatsService가 실제로 해석의 근거가 됩니다. 컴파일 타임에 생성자 파라미터 타입이 메타데이터로 남고, 컨테이너가 그 메타데이터를 읽어 어떤 provider를 넣을지 결정하죠. 관례적으로 타입으로 주입하지만, 특정 토큰에 @Inject()를 써서 이름으로 주입할 수도 있어요.
커스텀 provider
내장 IoC 컨테이너는 흔한 생성자 주입보다 더 많은 것을 지원합니다. provider를 값, 클래스, 동기·비동기 팩토리 등 여러 방식으로 정의할 수 있어요. 예를 들어 @Optional()을 함께 쓰면 주입할 대상이 없어도 오류 없이 넘어가는 선택적 provider를 만들 수 있습니다.
@Injectable()
export class HttpService<T> {
constructor(
@Optional() @Inject('HTTP_OPTIONS') private httpClient: T,
) {}
}
모듈에 등록해야 주입 가능
provider를 정의했다고 끝나는 게 아니에요. 모듈 파일에서 @Module()의 providers 배열에 등록해야 컨테이너가 의존성을 해석할 수 있습니다. 등록하기 전까지는 컨트롤러가 주입받을 대상이 없는 상태죠.
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class AppModule {}
만약 부트스트랩 과정에서 provider를 직접 꺼내 써야 한다면 Standalone Application처럼 별도 진입점을 활용하기도 해요.
실제 적용 (데이터스케쳐스 관점)
웹빌더 백엔드에서 provider는 표현 계층 아래의 응용·도메인 로직을 담당해요.
- 서비스로 비즈니스 로직 분리 — LLM 호출, 큐 작업, 검증 같은 로직을
@Injectable()서비스로 떼어 두고 컨트롤러는 요청 위임만 해요. - 구현 갈아 끼우기 — 같은 인터페이스 뒤에 여러 구현을 두고 DI로 바꿔 끼워서 테스트나 벤더 전환을 쉽게 해요.
- 팩토리 provider 활용 — 환경에 따라 달라지는 설정이나 연결을 팩토리 provider로 만들어 유연하게 구성합니다.
더 알아보기
- 공식 문서 (1차): NestJS Providers
- 인접 챕터: 모듈과 의존성 주입 · 컨트롤러와 라우팅
- 상위 문서: NestJS (v10·v11)