본문 바로가기
카테고리 없음

[NestJS] Modules

by joy_95 2025. 5. 18.

NestJS에서 @Module() 데코레이터가 붙은 클래스는 하나의 모듈 단위를 의미한다. Nest는 이 @Module() 데코레이터에 포함된 메타데이터를 활용하여 애플리케이션 구조를 구성하고 관리한다.

기본 구조

모든 Nest 애플리케이션은 최소 하나의 모듈 (AppModule)을 가지고 있으며, Nest는 이 루트 모듈을 기준으로 내부적으로 의존성 그래프 (Dependency Graph) 를 만들어 컴포넌트 간의 관계를 관리한다.


@Module() 데코레이터의 속성

@Module({
  providers: [CatsService],
  controllers: [CatsController],
  imports: [OtherModule],
  exports: [CatsService],
})
  • providers: Nest의 DI 컨테이너가 인스턴스화할 서비스 목록 (예: CatsService)
  • controllers: 라우팅 역할을 하는 컨트롤러 목록 (예: CatsController)
  • imports: 이 모듈에서 사용하려는 다른 모듈들
  • exports: 이 모듈을 가져가는 다른 모듈이 사용할 수 있도록 공개하는 provider

기본적으로 모듈 내부의 provider는

해당 모듈에서만 접근 가능


Feature Module

기능 단위로 모듈을 나누는 구조이다. 예: CatsController, CatsService는 고양이 관련 기능을 담당하므로 CatsModule에 묶는다.

// cats.module.ts
@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {}

루트 모듈에 아래처럼 추가한다:

// app.module.ts
@Module({
  imports: [CatsModule],
})
export class AppModule {}

이런 식으로 기능별로 디렉토리와 모듈을 구성해 코드를 조직화한다.


Shared Module

Nest의 모듈은 기본적으로 singleton이다. 즉, 하나의 인스턴스를 여러 모듈에서 공유할 수 있다.

공유하고 싶은 provider를 exports 에 추가해야 함:

@Module({
  controllers: [CatsController],
  providers: [CatsService],
  exports: [CatsService], // 공유!
})
export class CatsModule {}

이제 CatsModule을 다른 모듈이 imports로 가져오기만 하면, 그 모듈에서 CatsService를 사용할 수 있고 같은 인스턴스를 공유한다.

만약 각 모듈에서 CatsService를 개별 등록하면, 서로 다른 인스턴스를 사용하게 되어 상태 불일치 및 메모리 낭비가 발생할 수 있다.


Module Re-exporting

다른 모듈을 가져오는 것뿐 아니라, 가져온 모듈을 다시 export할 수 있다:

@Module({
  imports: [CommonModule],
  exports: [CommonModule],
})
export class CoreModule {}

CoreModule을 가져오는 모듈은 CommonModule의 provider까지 함께 사용할 수 있게 된다.


모듈 클래스에 provider 주입하기

모듈 클래스는 constructor를 통해 provider를 주입받을 수 있지만, 일반적으로 사용되지는 않는다:

export class CatsModule {
  constructor(private catsService: CatsService) {}
}

참고:

모듈 클래스 자체는 provider로 주입할 수 없다.


Global Module

어디서든 provider를 사용할 수 있도록 글로벌 범위로 등록하는 방법이다.

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

이제 다른 모듈에서 CatsService를 사용하려면 CatsModule을 imports에 명시하지 않아도 된다.

하지만

모든 모듈을 글로벌로 만드는 것은 좋은 설계가 아니다


Dynamic Module

Dynamic Module은 런타임에서 설정을 받아 모듈을 구성할 수 있도록 해주는 유연한 모듈이다.

구조 예시:

@Module({
  providers: [Connection],
  exports: [Connection],
})
export class DatabaseModule {
  static forRoot(entities = [], options?): DynamicModule {
    const providers = createDatabaseProviders(options, entities);
    return {
      module: DatabaseModule,
      providers: providers,
      exports: providers,
    };
  }
}
  • forRoot() 메서드가 설정을 받고 동적으로 provider를 생성해 반환한다.
  • Connection은 정적으로 선언되어 있고,
  • 추가적으로 forRoot를 통해 받은 설정으로 동적 provider를 생성한다.

사용 시:

@Module({
  imports: [DatabaseModule.forRoot([UserEntity])],
})
export class AppModule {}

이렇게 하면 UserEntity 기반의 설정이 적용된 DatabaseModule이 앱 전체에 적용된다.

동적 모듈을 글로벌로 만들기

return {
  global: true,
  module: DatabaseModule,
  providers: providers,
  exports: providers,
}

이렇게 하면 모든 모듈에서 자동으로 이 provider들을 사용할 수 있다.

re-export

@Module({
  imports: [DatabaseModule.forRoot([User])],
  exports: [DatabaseModule], // forRoot()는 호출하지 않고 모듈 자체를 export
})
export class AppModule {}
반응형