콘텐츠로 이동

NestJS 모듈(Module)과 의존성 주입

개요

NestJS 백엔드가 인증, 알림, 주문 같은 기능들이 한 파일에 섞여 있으면 어느 코드가 어디서 쓰이는지 금방 헤매요. NestJS는 이런 혼란을 모듈(Module)이라는 경계로 정리합니다. 모듈은 @Module() 데코레이터를 단 클래스인데, 이 데코레이터에 적힌 메타데이터가 Nest에게 애플리케이션 구조를 어떻게 잡을지 알려줘요. 작은 앱은 루트 모듈 하나로 충분하지만, 대부분은 서로 밀접한 기능을 묶은 모듈을 여럿 만들어 쓰게 됩니다.

핵심 개념

@Module 데코레이터가 받는 네 가지 키

모듈은 크게 네 가지를 선언할 수 있어요.

  • providers — Nest 인젝터가 생성하고 최소한 이 모듈 안에서 공유되는 서비스들.
  • controllers — 이 모듈에서 인스턴스화할 컨트롤러들.
  • imports — 이 모듈에서 필요한 provider를 내보내는 다른 모듈 목록.
  • exports — 이 모듈이 외부에 공개할 provider 목록.

핵심은 모듈이 기본적으로 provider를 캡슐화한다는 점이에요. 현재 모듈에 속한 provider나, 다른 모듈에서 명시적으로 내보낸 provider만 주입할 수 있습니다. exports에 적힌 provider가 곧 그 모듈의 공개 API가 되는 셈이죠.

기능 모듈(Feature Module)

관련된 기능을 한 모듈로 묶는 걸 기능 모듈이라고 해요. 예를 들어 고양이 관리 기능이라면 CatsControllerCatsService를 하나의 CatsModule에 함께 둡니다.

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {}

공유 모듈과 전역 모듈

CatsService를 다른 모듈에서도 쓰고 싶다면 exports에 넣어 공유 모듈로 만듭니다. 그런데 매번 import 하기가 번거로울 때는 @Global() 데코레이터로 모듈을 전역으로 선언할 수 있어요. 전역 모듈의 provider는 어디서든 바로 주입받을 수 있죠. DB 연결 같은 곳곳에서 필요한 것들을 전역으로 두면 편합니다. Angular와 달리 Nest는 provider를 기본적으로 모듈 범위 안에 가둬 두기 때문에, 정말 전역이 필요한 경우에만 @Global()을 쓰는 구조예요.

동적 모듈(Dynamic Module)

모듈을 만들 때 옵션에 따라 provider를 다르게 노출하고 싶다면 동적 모듈을 사용합니다. static forRoot(...) 같은 팩토리 메서드를 만들어서 인자에 따라 반환할 provider 목록을 바꾸는 방식이에요. 예를 들어 DatabaseModule.forRoot(entities, options)처럼 호출하면 전달받은 엔티티에 맞춰 저장소 provider들을 만들 수 있어요. 값이 정해진 이후에 구조를 결정해야 하는 설정성 라이브러리에서 자주 보이는 패턴입니다.

실제 적용 (데이터스케쳐스 관점)

웹빌더 백엔드는 계층형 구조 아래에서 기능을 모듈로 나눠 여러 작업이 독립적으로 진행돼요.

  • 기능별 모듈 분리 — 인증, LLM 연동, 큐 처리 같은 기능을 각각 하나의 모듈로 묶어 개발 경계를 명확히 해요.
  • 동적 모듈로 공통 인프라 추상화 — 저장소나 외부 연동을 forRoot 방식으로 받아 여러 환경(개발·운영)에서 같은 코드로 다르게 구성합니다.
  • 전역 모듈은 신중히 — 로깅이나 설정처럼 정말 어디서나 필요한 것만 @Global()로 열어두고 나머지는 explicit import로 유지해요.

더 알아보기