Dev Study
← 解説「動的モジュール — forRoot パターン」に戻る

サンプルコードで身につける: 動的モジュール — forRoot パターン

解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。

1静的メソッドが DynamicModule を返す

動的モジュールの骨格は、forRoot のような静的メソッドが { module, providers, exports } という DynamicModule オブジェクトを返すことです。@Module({}) の中身を、メソッドの中で実行時に組み立てて返すのが通常モジュールとの違いです。

import { Module, DynamicModule } from "@nestjs/common";

@Module({})
export class GreetingModule {
  // 静的メソッドが構成を組み立てて返す
  static forRoot(): DynamicModule {
    return {
      module: GreetingModule,
      providers: [GreetingService],
      exports: [GreetingService],
    };
  }
}

// 利用側は @Module の imports でメソッドを呼ぶ
// imports: [GreetingModule.forRoot()]

2options を useValue でトークンに載せる

forRoot が受け取った設定を、useValue でトークンに載せてモジュール内のサービスに注入可能にする定番の流れです。呼び出し側から渡した bucket が、サービスから @Inject 経由で読めるようになります。

import { Module, DynamicModule, Injectable, Inject } from "@nestjs/common";

export type StorageOptions = { bucket: string };

@Injectable()
export class StorageService {
  constructor(@Inject("STORAGE_OPTIONS") private opts: StorageOptions) {}
  bucketName() {
    return this.opts.bucket;
  }
}

@Module({})
export class StorageModule {
  static forRoot(options: StorageOptions): DynamicModule {
    return {
      module: StorageModule,
      // 渡された設定をトークンに載せて注入可能にする
      providers: [
        { provide: "STORAGE_OPTIONS", useValue: options },
        StorageService,
      ],
      exports: [StorageService],
    };
  }
}

3forRoot パターンの本質をプレーンTSで再現

「設定を受け取り、その場で providers を組み立て、トークンに載せた値をサービスへ注入する」という forRoot の核心を、デコレータなしの関数で再現した実行例です。動的モジュールが実行時に構成を生成しているだけだと分かります。

TypeScript

4forRoot と forFeature の使い分け

全体設定は forRoot で一度だけ、機能ごとに繰り返す登録は forFeature で、という慣習を示します。forFeature は全体設定を再受け取りせず、対象だけを受け取って軽量な構成を返すのが定石です。

import { Module, DynamicModule } from "@nestjs/common";

@Module({})
export class OrmModule {
  // 全体設定: AppModule で一度だけ。接続などを構築する
  static forRoot(options: { url: string }): DynamicModule {
    return {
      module: OrmModule,
      global: true, // 全体で使えるようにする
      providers: [{ provide: "ORM_CONNECTION", useValue: options }],
      exports: ["ORM_CONNECTION"],
    };
  }

  // 機能ごと: 各機能モジュールで繰り返す。設定は再受け取りしない
  static forFeature(entities: string[]): DynamicModule {
    return {
      module: OrmModule,
      providers: [{ provide: "ORM_ENTITIES", useValue: entities }],
      exports: ["ORM_ENTITIES"],
    };
  }
}
// AppModule:   imports: [OrmModule.forRoot({ url })]
// UsersModule: imports: [OrmModule.forFeature(["User"])]

5実務: ConfigModule.forRoot 風の動的モジュール

ConfigModule.forRoot({ isGlobal, envFilePath }) を自作するイメージの実務例です。読み込んだ設定を ConfigService に載せ、global: true でアプリ全体から再 import なしに使えるようにする、というよく見る形を再現します。

import { Module, DynamicModule, Injectable, Inject } from "@nestjs/common";

export type ConfigOptions = { isGlobal?: boolean; values: Record<string, string> };

@Injectable()
export class ConfigService {
  constructor(@Inject("CONFIG_VALUES") private values: Record<string, string>) {}
  get(key: string): string | undefined {
    return this.values[key];
  }
}

@Module({})
export class ConfigModule {
  static forRoot(options: ConfigOptions): DynamicModule {
    return {
      module: ConfigModule,
      global: options.isGlobal, // isGlobal: true で全体から使える
      providers: [
        { provide: "CONFIG_VALUES", useValue: options.values },
        ConfigService,
      ],
      exports: [ConfigService],
    };
  }
}
// imports: [ConfigModule.forRoot({ isGlobal: true, values: process.env })]