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 배열에 등록해야 컨테이너가 의존성을 해석할 수 있습니다. 등록하기 전까지는 컨트롤러가 주입받을 대상이 없는 상태죠.
만약 부트스트랩 과정에서 provider를 직접 꺼내 써야 한다면 Standalone Application처럼 별도 진입점을 활용하기도 해요.
실제 적용 (데이터스케쳐스 관점)¶
웹빌더 백엔드에서 provider는 표현 계층 아래의 응용·도메인 로직을 담당해요.
- 서비스로 비즈니스 로직 분리 — LLM 호출, 큐 작업, 검증 같은 로직을
@Injectable()서비스로 떼어 두고 컨트롤러는 요청 위임만 해요. - 구현 갈아 끼우기 — 같은 인터페이스 뒤에 여러 구현을 두고 DI로 바꿔 끼워서 테스트나 벤더 전환을 쉽게 해요.
- 팩토리 provider 활용 — 환경에 따라 달라지는 설정이나 연결을 팩토리 provider로 만들어 유연하게 구성합니다.
더 알아보기¶
- 공식 문서 (1차): NestJS Providers
- 인접 챕터: 모듈과 의존성 주입 · 컨트롤러와 라우팅
- 상위 문서: NestJS (v10·v11)