NESTJS를 배워보자(14) - Dynamic modules

yoon·2023년 8월 30일
0

NESTJS를 배워보자

목록 보기
14/21

Dynamic modules

nest의 공식문서를 토대로 작성합니다.

이전 모듈 챕터에서 동적 모듈을 간략하게 소개했습니다. 이 챕터에서는 동적 모듈에 대해 더 자세히 봅니다. 이 챕터가 끝나면 동적 모듈이 무엇이며 언제 어떻게 사용하는지 잘 알게 될 것입니다.

Introduction

이제까지 봤던 대부분의 코드 예제는 일반 모듈 또는 정적 모듈을 사용했습니다. 익숙한 예제를 볼까요.

먼저 UsersService를 제공하고 내보내는 UsersModule을 정의하겠습니다. UsersModuleUsersService의 호스트 모듈입니다.

import { Module } from '@nestjs/common';
import { UsersService } from './users.service';

@Module({
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}

다음으로 UsersModule을 가져와서 UsersModule이 내보낸 프로바이더를 AuthModule내에서 사용할 수 있게 만드는 AuthModule을 정의합니다.

역시 저는 CLI로 module, controller, service 순으로 생성하고 작성하겠습니다.

$ nest g mo auth
$ nest g co auth
$ nest g s auth

참고로 저는 기존 users 관련 파일들을 user로 생성해서 UsersModule이 아닌 UserModule 등 모두 users가 아닌 user로 사용합니다;;

import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { UsersModule } from '../users/users.module';

@Module({
  imports: [UsersModule],
  providers: [AuthService],
  exports: [AuthService],
})
export class AuthModule {}

이러한 구성을 사용하면 AuthModule에 호스팅되는 AuthServiceUsersService를 삽입할 수 있습니다.

import { Injectable } from '@nestjs/common';
import { UsersService } from '../users/users.service';

@Injectable()
export class AuthService {
  constructor(private usersService: UsersService) {}
  /*
    Implementation that makes use of this.usersService
  */
}

이를 정적 모듈 바인딩이라고 합니다. Nest가 모듈을 서로 연결하는 데 필요한 모든 정보는 호스트와 소비 모듈에 이미 선언되어 있습니다. 이 과정에서 어떤 일이 일어나는지 살펴봅시다. Nest는 AuthModule 내에서 UsersService를 다음과 같이 사용할 수 있도록 합니다:

  1. UsersModule 자체가 소비하는 다른 모듈을 일시적으로 가져오고 종속성을 일시적으로 해결하는 것을 포함하여 UsersModule을 인스턴스화합니다.

  2. AuthModule을 인스턴스화하고, UsersModule에서 내보낸 프로바이더를 AuthModule의 컴포넌트에서 사용할 수 있도록 설정합니다(AuthModule에서 선언된 것처럼).

  3. AuthServiceUsersService 인스턴스 삽입.

Dynamic module use case

동적 모듈의 좋은 예로 Nest의 configuration 모듈을 들 수 있습니다. 많은 애플리케이션에서 configuration 모듈을 사용하여 configuration 세부 정보를 외부화하는 것이 유용합니다. 이렇게 하면 개발자를 위한 개발 데이터베이스, 스테이징/테스트 환경을 위한 스테이징 데이터베이스 등 다양한 배포에서 애플리케이션 설정을 동적으로 쉽게 변경할 수 있습니다. configuration 매개변수 관리를 configuration 모듈에 위임하면 애플리케이션 소스 코드는 configuration 매개변수와 독립적으로 유지됩니다.

문제는 configuration 모듈 자체가 일반적('플러그인'과 유사)이기 때문에 이를 사용하는 모듈에 따라 사용자 정의해야 한다는 점입니다. 바로 이때 동적 모듈이 등장합니다. 동적 모듈 기능을 사용하면 configuration 모듈을 동적으로 만들어 소비 모듈이 API를 구성하여 configuration 모듈을 가져올 때 configuration 모듈이 사용자 지정되는 방식을 제어할 수 있습니다.

즉, 동적 모듈은 지금까지 살펴본 정적 바인딩을 사용하는 것과 달리 한 모듈을 다른 모듈로 가져오고 가져온 모듈의 속성 및 동작을 사용자 정의할 수 있는 API를 제공합니다.

저는 이 글만 읽고는 확 와닿지 않네요. 실제 사용 사례를 봐야 더 잘 이해할 수 있을 것 같습니다. 공식문서를 더 읽어봅시다!

Config module example

이 섹션에서는 configuration 챕터에 있는 예제 코드의 기본 버전을 사용합니다. 이 챕터의 마지막에 완성된 버전은 여기에서 작업 예제로 사용할 수 있습니다.

우리의 요구 사항은 ConfigModule이 옵션 객체를 받아들여 사용자 정의하도록 하는 것입니다. 지원하고자 하는 기능은 다음과 같습니다. 기본 샘플은 프로젝트 루트 폴더에 있는 .env 파일의 위치를 하드코딩합니다. 이를 구성할 수 있도록 하여 원하는 폴더에서 .env 파일을 관리할 수 있도록 합니다. 예를 들어 프로젝트 루트 아래 config라는 폴더에 다양한 .env 파일을 저장하고 싶다고 가정합니다. 다른 프로젝트에서 ConfigModule을 사용할 때 다른 폴더를 선택할 수 있길 원합니다.

동적 모듈을 사용하면 가져오는 모듈에 매개변수를 전달하여 동작을 변경할 수 있습니다. 어떻게 작동하는지 봅시다. 사용하는 모듈의 관점에서 어떻게 보일지에 대한 최종 목표에서 시작하여 거꾸로 작업하는 것이 도움이 됩니다. 먼저 정적으로 ConfigMudle을 import하는 예제를 빠르게 살펴봅시다. @Module() 데코레이터의 import 배열을 자세히 봅시다.

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from './config/config.module';

@Module({
  imports: [ConfigModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

이제 동적 예제를 보고 차이를 비교해봅시다:

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from './config/config.module';

@Module({
  imports: [ConfigModule.register({ folder: './config' })],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

위의 동적 예시에서 어떤 일이 일어나는지 살펴봅시다.

  1. ConfigModule은 일반 클래스이므로 register()라는 정적 메소드가 있어야 한다는 것을 유추할 수 있습니다. 이 메소드가 정적 메소드라는 것을 알 수 있는 이유는 클래스의 인스턴스가 아닌 ConfigModule 클래스에서 호출하기 때문입니다.

  2. register() 메소드는 우리가 정의한 것이므로 원하는 입력 인수를 받을 수 있습니다. 이 경우 적절한 속성을 가진 간단한 옵션 객체를 받아들이는 것이 일반적인 경우입니다.

  3. register() 메소드는 모듈 같은 것을 반환해야 한다는 것을 유추할 수 있습니다.

실제로 register() 메소드가 반환하는 것은 DynamicModule입니다. 동적 모듈은 런타임에 생성되는 모듈로 정적 모듈과 동일한 프로퍼티에 module이라는 프로퍼티가 하나 더 추가된 것에 불과합니다. 데코레이터에 전달된 모듈 옵션을 주의 깊게 살펴보면서 샘플 정적 모듈 선언을 빠르게 살펴봅시다.

@Module({
  imports: [DogsModule],
  controllers: [CatsController],
  providers: [CatsService],
  exports: [CatsService]
})

동적 모듈은 정확히 동일한 인터페이스를 가진 객체와 module이라는 추가 프로퍼티 하나를 반환해야 합니다. module 속성은 모듈의 이름 역할을 하며 아래 예시와 같이 모듈 클래스 이름과 동일해야 합니다.

HINT
동적 모듈의 경우 모듈 옵션 객체의 모든 속성은 module을 제외하고 선택사항임.

정적 register() 메소드는 어떨까요? 이제 이 메소드의 임무가 DynamicModule 인터페이스를 가진 객체를 반환하는 것임을 알 수 있습니다. 이 메소드를 호출하면 정적인 경우 모듈 클래스 이름을 나열하는 방식과 유사하게 임포트 목록에 모듈을 효과적으로 제공합니다. 즉 동적 모듈 API는 단순히 모듈을 반환하지만 @Module() 데코레이터에서 프로퍼티를 수정하는 대신 프로그래밍 방식으로 프로퍼티를 지정합니다.

동적 모듈 바인딩을 완성하는 데 도움이 되는 몇 가지 세부 사항이 더 있습니다.

  1. 이제 @Module() 데코레이터의 imports 프로퍼티는 모듈 클래스 이름(ex: imports: [UsersModule])뿐만 아니라 동적 모듈을 반환하는 함수(ex: imports: [ConfigModule.register(...)])도 받을 수 있음.

  2. 동적 모듈은 자체적으로 다른 모듈을 import할 수 있음. 이 예에서는 그렇게 하지 않지만 동적 모듈이 다른 모듈의 프로바이더에 의존하는 경우 선택적 imports 속성을 사용하여 해당 프로바이더를 import할 수 있음. 다시 말하지만 이는 @Module() 데코레이터를 사용하여 정적 모듈에 대한 메타데이터를 선언하는 방식과 정확히 유사함.

이러한 이해를 바탕으로 이제 동적 ConfigModule 선언이 어떤 모습이어야 하는지 살펴봅시다.

여기도 위와 동일하게 생성해줍니다!

import { DynamicModule, Module } from '@nestjs/common';
import { ConfigService } from './config.service';

@Module({})
export class ConfigModule {
  static register(): DynamicModule {
    return {
      module: ConfigModule,
      providers: [ConfigService],
      exports: [ConfigService],
    };
  }
}

ConfigModule.register(...)를 호출하면 지금까지 @Module() 데코레이터를 통해 메타데이터로 제공한 것과 본질적으로 동일한 프로퍼티를 가진 DynamicModule 객체가 반환됩니다.

Module configuration

위에서 추측한 것처럼 정적 register() 메소드에 options 객체를 전달하는 것이 ConfigModule의 동작을 커스터마이징하는 가장 확실한 해결책입니다. 소비 모듈의 imports 프로퍼티를 살펴봅시다:

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from './config/config.module';

@Module({
  imports: [ConfigModule.register({ folder: './config' })],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

위에서 register() 메소드에서 아무 인수도 받지 않도록 작성했으므로 여기서는 에러가 날 것입니다. 일단 더 읽어봅시다!

이렇게 하면 options 객체를 동적 모듈에 전달하는 작업이 잘 처리됩니다. 그러면 이 options 객체를 ConfigModule에서 어떻게 사용할까요? 우리는 기본적으로 ConfigModule이 다른 프로바이더가 사용할 수 있도록 injectable 서비스인 ConfigService를 제공하고 내보내기 위한 호스트라는 것을 알고 있습니다. 실제로 동작을 사용자 정의하기 위해 options 객체를 읽어야 하는 것은 ConfigService입니다. 일단 register() 메소드에서 어떻게든 optionsConfigService로 가져오는 방법을 알고 있다고 가정해 봅시다. 이 가정에 따라 서비스를 몇 가지 변경하여 options 객체의 속성을 기반으로 동작을 사용자 지정할 수 있습니다.(당분간은 실제로 어떻게 전달할지 결정하지 않았으므로 옵션을 하드코딩 합니다. 곧 수정됩니다.)

import { Injectable } from '@nestjs/common';
import * as dotenv from 'dotenv';
import * as fs from 'fs';
import * as path from 'path';
import { EnvConfig } from './interfaces';

@Injectable()
export class ConfigService {
  private readonly envConfig: EnvConfig;

  constructor() {
    const options = { folder: './config' };

    const filePath = `${process.env.NODE_ENV || 'development'}.env`;
    const envFile = path.resolve(__dirname, '../../', options.folder, filePath);
    this.envConfig = dotenv.parse(fs.readFileSync(envFile));
  }

  get(key: string): string {
    return this.envConfig[key];
  }
}

저는 따로 dotenv를 설치했습니다

$ npm i --save dotenv

이제 ConfigServiceoptions에서 지정한 폴더에서 .env 파일을 찾는 방법을 알고 있습니다.

남은 작업은 register() 단계의 options 객체를 어떻게든 ConfigService에 주입하는 것입니다. 물론 이를 위해 의존성 주입을 사용할 것입니다. 이것이 핵심이므로 반드시 이해해야 합니다. 우리의 ConfigModuleConfigService를 제공하고 있습니다. ConfigService는 런타입에만 제공되는 options 객체에 따라 달라집니다. 따라서 런타임에 먼저 options 객체를 Nest IoC 컨테이너에 바인딩한 다음 Nest가 이를 ConfigService에 주입하도록 해야 합니다.
Custom provider 챕터에서 프로바이더는 서비스뿐만 아니라 모든 값을 포함할 수 있으므로 종속성 주입을 사용하여 간단한 옵션 객체를 처리해도 괜찮다고 했습니다.

먼저 options 객체를 IoC 컨테이너에 바인딩하는 방법을 살펴봅시다. 정적 register() 메소드에서 이 작업을 수행합니다. 모듈을 동적으로 구성하고 있으며 모듈의 속성 중 하나는 프로바이더 목록이라는 점을 기억하세요. 따라서 우리가 해야 할 일은 options 객체를 프로바이더로 정의하는 것입니다. 이렇게 하면 다음 단계에서 활용하게 될 ConfigService에 주입할 수 있게 됩니다.

import { DynamicModule, Module } from '@nestjs/common';
import { ConfigService } from './config.service';

@Module({})
export class ConfigModule {
  static register(options: Record<string, any>): DynamicModule {
    return {
      module: ConfigModule,
      providers: [
        {
          provide: 'CONFIG_OPTIONS',
          useValue: options,
        },
        ConfigService,
      ],
      exports: [ConfigService],
    };
  }
}

이제 ConfigService'CONFIG_OPTIONS' 프로바이더를 주입하여 프로세스를 완료할 수 있습니다. 클래스가 아닌 토큰을 사용하여 프로바이더를 정의할 때는 Custom provider 챕터에서 설명한 대로 @Inject() 데코레이터를 사용해야 합니다.

import * as dotenv from 'dotenv';
import * as fs from 'fs';
import * as path from 'path';
import { Injectable, Inject } from '@nestjs/common';
import { EnvConfig } from './interfaces';

@Injectable()
export class ConfigService {
  private readonly envConfig: EnvConfig;

  constructor(@Inject('CONFIG_OPTIONS') private options: Record<string, any>) {
    const filePath = `${process.env.NODE_ENV || 'development'}.env`;
    const envFile = path.resolve(__dirname, '../../', options.folder, filePath);
    this.envConfig = dotenv.parse(fs.readFileSync(envFile));
  }

  get(key: string): string {
    return this.envConfig[key];
  }
}

마지막 참고 사항: 간단하게 하기 위해 위에서 문자열 기반 인젝션 토큰 ('CONFIG_OPTIONS')을 사용했지만 가장 좋은 방법은 별도의 파일에 상수(또는 심볼)로 정의하고 해당 파일을 import하는 것입니다.
예:

export const CONFIG_OPTIONS = 'CONFIG_OPTIONS';

Example

이 챕터의 코드 예제

저도 쭉 따라하다가 안 되는 부분이 좀 있어서 여기서 참고해서 작성했습니다.ㅎㅎ

제 코드입니다!!
https://github.com/cxzaqq/cxzaqq-velog/tree/3.3-dynamic-modules

Community guidelines

이제까지 일부 @nestjs/ 패키지에서 forRoot, register, forFeature와 같은 메소드가 사용되는 것을 보셨을 것이고 이 모든 메소드의 차이점이 무엇인지 궁금하실 것입니다. 이에 대한 엄격한 규칙은 없지만 @nestjs/ 패키지는 다음 가이드라인을 따르려고 노력합니다:

모듈을 만들 때

  • register : 특정 configuration으로 동적 모듈을 구성하고 호출하는 모듈에서만 사용. 예를 들어 Nest의
    @nestjs/axios : HttpModule,register({ baseUrl: 'someUrl' }).
    다른 모듈에서 이것을 사용하면 다른 구성을 갖는다. 원하는 만큼 많은 모듈에 대해 이 작업을 수행 가능.

  • forRoot : 동적 모듈을 한 번 구성하면 여러 곳에서 해당 구성을 재사용 가능. 이것이 바로 하나의 GraphQLModule.forRoot(), TypeOrmModule.forRoot() 등이 있는 이유.

  • forFeature : 동적 모듈의 forRoot 구성을 사용할 것으로 예상되지만 호출 모듈의 필요에 따라 일부 구성을 수정해야 하는 경우.(예: 이 모듈이 액세스할 수 있는 레포지토리, 또는 로거가 사용해야 하는 컨텍스트)

이 모든 것에는 일반적으로 비동기 대응 함수인 registerAsync, forRootAsync, forFeatureAsync가 있으며 이는 같은 의미이지만 configuration에도 Nest의 의존성 주입을 사용합니다.

Configurable module builder

특히 초보자에게는 비동기 메소드(registerAsync, forRootAsync 등)를 노출하는 고도의 configurable 동적 모듈을 수동으로 생성하는 것이 매우 복잡하므로 Nest는 이 과정을 용이하게 하고 단 몇 줄의 코드만으로 모듈 설계도를 구성할 수 있는 ConfigurableModuleBuilder 클래스를 노출합니다.

예를 들어 위에서 사용한 예제를 ConfigurableModuleBuilder를 사용하도록 변환해 보겠습니다. 시작하기 전에 ConfigModule이 받는 옵션을 나타내는 전용 인터페이스를 만들어 보겠습니다.

export interface ConfigModuleOptions {
  folder: string;
}

이렇게 하면 기존 config.module.ts 파일과 함께 새로운 전용 파일을 생성하고 이름을 config.module-definition.ts로 지정합니다. 이 파일에서 ConfigurableModuleBuilder를 활용하여 ConfigModule 정의를 작성해 보겠습니다.

# config.module-definition.ts

import { ConfigurableModuleBuilder } from '@nestjs/common';
import { ConfigModuleOptions } from './interfaces/config-module-options.interface';

export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =
  new ConfigurableModuleBuilder<ConfigModuleOptions>().build();

이제 config.module.ts 파일을 열고 자동 생성된 ConfigurableModuleClass를 활용하도록 구현을 수정합니다.

import { Module } from '@nestjs/common';
import { ConfigService } from './config.service';
import { ConfigurableModuleClass } from './config.module-definition';

@Module({
  providers: [ConfigService],
  exports: [ConfigService],
})
export class ConfigModule extends ConfigurableModuleClass {}

ConfigurableModuleClass를 확장한다는 것은 이제 ConfigModuleregister 메소드뿐만 아니라 비동기 팩토리를 제공하여 소비자가 해당 모듈을 비동기적으로 구성할 수 있도록 하는 registerAsync 메소드도 제공한다는 의미입니다.

@Module({
  imports: [
    ConfigModule.register({ folder: './config' }),
    // or alternatively:
    // ConfigModule.registerAsync({
    //   useFactory: () => {
    //     return {
    //       folder: './config',
    //     }
    //   },
    //   inject: [...any extra dependencies...]
    // }),
  ],
})
export class AppModule {}

마지막으로 지금까지 사용한 'CONFIG_OPTIONS' 대신 생성된 모듈 옵션의 프로바이더를 주입하도록 ConfigService 클래스를 업데이트하겠습니다.

@Injectable()
export class ConfigService {
  constructor(@Inject(MODULE_OPTIONS_TOKEN) private options: ConfigModuleOptions) { ... }
}

Custom method key

ConfigurableModuleClass는 기본적으로 register와 그에 대응하는 registerAsync 메소드를 제공합니다. 다른 메소드 이름을 사용하려면 다음과 같이 ConfigurableModuleBuilder#setClassMethodName 메소드를 사용합니다:

# config.module-definition.ts

export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =
  new ConfigurableModuleBuilder<ConfigModuleOptions>().setClassMethodName('forRoot').build();

이 구조는 ConfigurableModuleBuilder가 대신 forRootforRootAsync를 노출하는 클래스를 생성하도록 지시합니다.
예:

@Module({
  imports: [
    ConfigModule.forRoot({ folder: './config' }), // <-- note the use of "forRoot" instead of "register"
    // or alternatively:
    // ConfigModule.forRootAsync({
    //   useFactory: () => {
    //     return {
    //       folder: './config',
    //     }
    //   },
    //   inject: [...any extra dependencies...]
    // }),
  ],
})
export class AppModule {}

Custom options factory class

registerAsync 메소드(또는 구성에 따라 forRootAsync 또는 다른 이름)를 사용하면 소비자가 모듈 구성을 확인하는 프로바이더 정의를 전달할 수 있으므로 라이브러리 소비자는 잠재적으로 구성 객체를 구성하는 데 사용할 클래스를 제공할 수 있습니다.

@Module({
  imports: [
    ConfigModule.registerAsync({
      useClass: ConfigModuleOptionsFactory,
    }),
  ],
})
export class AppModule {}

이 클래스는 기본적으로 모듈 구성 객체를 반환하는 create() 메소드를 제공해야 합니다. 그러나 라이브러리가 다른 명명 규칙을 따르는 경우 해당 동작을 변경하고 ConfigurableModuleBuilder#setFactoryMethodName 메소드를 사용하여 다른 메소드(예: createConfigOptions)를 받도록 ConfigurableModuleBuilder에 지시할 수 있습니다.

# config.module-definition.ts

export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =
  new ConfigurableModuleBuilder<ConfigModuleOptions>().setFactoryMethodName('createConfigOptions').build();

이제 ConfigModuleOptionsFactory 클래스는 create() 대신 createConfigOptions 메소드를 노출해야 합니다:

@Module({
  imports: [
    ConfigModule.registerAsync({
      useClass: ConfigModuleOptionsFactory, // <-- this class must provide the "createConfigOptions" method
    }),
  ],
})
export class AppModule {}

Extra options

모듈의 동작 방식을 결정하는 추가 옵션이 필요하지만 동시에 MODULE_OPTIONS_TOKEN 프로바이더에 포함되지 않아야 하는 예외적인 경우가 있습니다(해당 모듈 내에 등록된 서비스/프로바이더와 관련이 없으므로, 예를 들어 ConfigService는 호스트 모듈이 전역 모듈로 등록되어 있는지 알 필요가 없음).

이런 경우 ConfigurableModuleBuilder#setExtras 메소드를 사용할 수 있습니다.

export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = new ConfigurableModuleBuilder<ConfigModuleOptions>()
  .setExtras(
    {
      isGlobal: true,
    },
    (definition, extras) => ({
      ...definition,
      global: extras.isGlobal,
    }),
  )
  .build();

위의 예에서 setExtras 메소드에 전달된 첫 번째 인수는 "extra" 속성에 대한 기본값이 포함된 객체입니다. 두 번째 인수는 자동 생성된 모듈 정의(프로바이더, 내보내기 등 포함)와 추가 속성(소비자가 지정하거나 기본값)을 나타내는 추가 객체를 취하는 함수입니다. 이 함수의 반환 값은 수정된 모듈 정의입니다. 이 특정 예제에서는 extras.isGlobal 속성을 가져와 모듈 정의의 전역 속성에 할당합니다.

이제 이 모듈을 사용할 때 다음과 같이 추가적으로 isGlobal 플래그를 전달할 수 있습니다.

@Module({
  imports: [
    ConfigModule.register({
      isGlobal: true,
      folder: './config',
    }),
  ],
})
export class AppModule {}

그러나 isGlobal은 "extra" 속성으로 선언되었으므로 MODULE_OPTIONS_TOKEN 프로바이더에서 사용할 수 없습니다

@Injectable()
export class ConfigService {
  constructor(@Inject(MODULE_OPTIONS_TOKEN) private options: ConfigModuleOptions) {
    // "options" object will not have the "isGlobal" property
    // ...
  }
}

Extending auto-generated methods

자동 생성된 정적 메소드(register, registerAsync 등)는 필요한 경우 다음과 같이 확장할 수 있습니다.

import { Module } from '@nestjs/common';
import { ConfigService } from './config.service';
import { ConfigurableModuleClass, ASYNC_OPTIONS_TYPE, OPTIONS_TYPE } from './config.module-definition';

@Module({
  providers: [ConfigService],
  exports: [ConfigService],
})
export class ConfigModule extends ConfigurableModuleClass {
  static register(options: typeof OPTIONS_TYPE): DynamicModule {
    return {
      // your custom logic here
      ...super.register(options),
    };
  }

  static registerAsync(options: typeof ASYNC_OPTIONS_TYPE): DynamicModule {
    return {
      // your custom logic here
      ...super.registerAsync(options),
    };
  }
}

모듈 정의 파일에서 내보내야 하는 OPTIONS_TYPEASYNC_OPTIONS_TYPE 유형 사용에 유의하세요.

export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE, ASYNC_OPTIONS_TYPE } = new ConfigurableModuleBuilder<ConfigModuleOptions>().build();

고생하셨습니다!
다음 글에서 만나요~~😀


저도 아직 배우는 단계입니다. 지적 감사히 받겠습니다. 함께 열심히 공부해요!!

profile
백엔드 개발자 지망생

0개의 댓글