Moduleとは

Moduleとは

NestJSにおけるModuleとは、@Module()デコレータでアノテーションされたクラスを意味します。@Module()デコレータは、NestJSがアプリケーションの構造を整理するために利用するメタデータを提供します。
各アプリケーションには、少なくとも1つのModule(ルートモジュール)があります。ルートモジュールとは、Nestがアプリケーショングラフを構築するための出発点であり、NestがModuleProviderの関係や依存関係を解決するために使用する内部データの構造です。非常に小さなアプリケーションでは、理論的にはルートモジュールだけでいいかもしれません。しかし、これは良いケースだと言えません。
Moduleはコンポーネントを組織化する上では非常に効果的な方法です。ほとんどのアプリケーションでは、結果として得られるアーキテクチャは複数のモジュールを採用し、それぞれが密接に関連した機能のセットをカプセル化することになります。
@Module()デコレータは、Moduleのプロパティを記述したオブジェクトを一つだけ受け取ります。
NestJSにおけるModuleは以下の4つの要素で構成されています。
  • providers:Nestインジェクタによってインスタンス化され、少なくともこのModule全体で共有される可能性があるProvider。
  • controllers:このModuleで定義された、インスタンス化する必要のあるControllerのセット。
  • imports:このModuleで必要とされるProviderをエクスポートするインポートモジュールのリスト。
  • exports:このモジュールが提供するProviderのサブセットで、このModuleをインポートしている他のModuleで利用可能であるべきものを指定するリスト。Provider自体。またはそのトークンだけを使える。
このModuleは、デフォルトでProviderをカプセル化します。これは、現在のModuleの直接の一部でもなく、インポートしているモジュールからエクスポートされたものでもないProviderを注入することは不可能であることを示します。したがって、あるModuleからエクスポートされたProviderは、そのModuleのAPIとみなせます。
次のようにして、CatsModuleを実装できます。
cats/cats.module.ts
import { Module } from '@nestjs/common'
import { CatsController } from './cats.controller'
import { CatsService } from './cats.service'
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {}
メモ
NestJSでModuleを作る時、以下のコマンドを実行してください。 nest g module cats
上記では、cats.module.tsファイルでCatsModuleを定義し、このModuleに関するものをすべてcatsディレクトリに移動させました。最後に、このModuleをルートモジュール(app.module.tsファイルで定義されているAppModule)にインポートしてください。
ディレクトリは以下のようになります。
src
 cats
  dto
   create-cat.dto.ts
  interfaces
   cat.interface.ts
  cats.controller.ts
  cats.module.ts
  cats.service.ts
app.module.ts
main.ts

Moduleをシェアする

NestJSでは、Moduleはデフォルトでシングルトンなので、複数のModule間で任意のプロバイダの同じインスタンスを簡単に共有することができます。
すべてのModuleは自動的に共有モジュールとなります。一度作成したモジュールは、どのモジュールでも再利用できます。例えば、CatsServiceのインスタンスを他のモジュールと共有したいとします。そのためには、まずCatsServiceプロバイダをエクスポートして、以下のようにモジュールのexports配列に追加する必要があります。
cats.module.ts
import { Module } from '@nestjs/common'
import { CatsController } from './cats.controller'
import { CatsService } from './cats.service'
@Module({
controllers: [CatsController],
providers: [CatsService],
exports: [CatsService]
})
export class CatsModule {}
これで、CatsModuleをインポートしたモジュールは全てCatsServiceにアクセスできるようになり、同様にインポートした他のすべてのModuleと同じインスタンスを共有できます。

Moduleの再エクスポート

上述で見たように、Moduleは内部のプロバイダをエクスポートできます。さらに、インポートしたModuleを再エクスポートできます。次の例では、CommonModuleCoreModuleにインポートされ、CoreModuleからもエクスポートされます。
@Module({
imports: [CommonModule],
exports: [CommonModule],
})
export class CoreModule {}
Moduleクラスは、DIパターンを実装できます。(DIパターンについてはこちらを参照)
cats.module.ts
import { Module } from '@nestjs/common'
import { CatsController } from './cats.controller'
import { CatsService } from './cats.service'
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {
constructor(private catsService: CatsService) {}
}

グローバルModule

もし、どこでも同じModuleのセットをインポートする必要がある場合は面倒になります。NestJSでは、ProviderはModuleのスコープ内にカプセル化されます。カプセル化されたModuleをインポートしないと、そのモジュールのProviderを他の場所で使用できません。
すぐにどこでも使えるProviderを提供したい場合は、@Global()デコレータを活用してModuleをグローバル化します。
import { Module, Global } from '@nestjs/common'
import { CatsController } from './cats.controller'
import { CatsService } from './cats.service'
@Global()
@Module({
controllers: [CatsController],
providers: [CatsService],
exports: [CatsService],
})
export class CatsModule {}
デコレータを使うと、Moduleをグローバルに展開できます。グローバルModuleは一度だけ登録する必要があり、一般的にはルートモジュールやコアモジュールによって登録されます。上記の例では、CatsServiceプロバイダはどこにでもあるもので、サービスをインジェクトしたいModuleは、imports配列でCatsModuleをインポートする必要はありません。
メモ すべてのModuleをグローバルにすることは良い設計判断とはいえません。グローバルModuleは、必要な定型句を減らすために用意されています。imports配列は、一般的にModuleのAPIをコンシューマが利用できるようにするために望ましい方法です。

動的Module

NestJSのModuleシステムには、動的Moduleという強力な機能があります。この機能で、Providerを動的に登録・設定できるカスタマイズ可能なModuleを簡単に作成できます。動的Moduleは、ここで広範囲にカバーされます。この章では、Moduleの紹介を完了するために、簡単に概要を説明します。
以下は、DataabaseModuleの動的Module定義の例です。
import { Module, DynamicModule } from '@nestjs/common'
import { createDatabaseProviders } from './database.providers'
import { Connection } from './connection.provider'
@Module({
providers: [Connection],
})
export class DatabaseModule {
static forRoot(entities = [], options?): DynamicModule {
const providers = createDatabaseProviders(options, entities);
return {
module: DatabaseModule,
providers: providers,
exports: providers,
};
}
}
メモ forRoot()メソッドは同期または非同期で動的Moduleを返せます。
このModuleは、デフォルトでConnectionプロバイダを定義しています。さらに、forRoot()メソッドに渡されるエンティティやオプション・オブジェクトに応じて、Providerのコレクション(リポジトリ)を公開します。動的Moduleが返すプロパティは、@Module()デコレータのメタデータを拡張することに注意してください。
このようにして、静的に宣言されたConnectionプロバイダと動的に生成されたリポジトリプロバイダの両方がModuleからエクスポートされます。
動的Moduleをグローバルスコープに登録したい場合は、globalプロパティをtrueに設定します。
{
global: true,
module: DatabaseModule,
providers: providers,
exports: providers,
}
注意
前述したように、すべてのModuleをグローバル化するのは設計上良い判断とはいえません。
DatabaseModuleのインポート及び設定は、以下の手順で実施できます。
import { Module } from '@nestjs/common';
import { DatabaseModule } from './database/database.module';
import { User } from './users/entities/user.entity';
@Module({
imports: [DatabaseModule.forRoot([User])],
})
export class AppModule {}
動的Moduleを順番に再エクスポートする場合は、exports配列のforRoot()メソッド呼び出しを省略できます。
import { Module } from '@nestjs/common'
import { DatabaseModule } from './database/database.module'
import { User } from './users/entities/user.entity'
@Module({
imports: [DatabaseModule.forRoot([User])],
exports: [DatabaseModule],
})
export class AppModule {}