Dev Study
← 解説「モジュール間の共有 — exports と imports」に戻る

サンプルコードで身につける: モジュール間の共有 — exports と imports

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

1exports の最小例

providers と exports の違いを示す最小例です。providers は「このモジュールの持ち物」、exports は「外に貸し出してよい物」のリストであり、両方に載せて初めて他モジュールから使えます。

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

@Module({
  providers: [UsersService], // 登録: モジュール内で使える
  exports: [UsersService],   // 公開: 外のモジュールからも使える
})
export class UsersModule {}

// providers = 持ち物リスト / exports = 貸し出してよい物リスト
// exports に載せない限り、外のモジュールには見えない

2利用する側の imports

公開されたサービスを別モジュールから使う側のコードです。サービス単体ではなくモジュールごと imports で取り込む、という NestJS 特有のルールを「注文がユーザー情報を参照する」典型場面で示します。

import { Injectable, Module } from "@nestjs/common";
import { UsersModule } from "../users/users.module";
import { UsersService } from "../users/users.service";

@Injectable()
export class OrdersService {
  // UsersModule が export しているので注入できる
  constructor(private readonly usersService: UsersService) {}

  createOrder(userId: number) {
    // 注文時にユーザー情報を参照する、という機能間連携
    return { userId };
  }
}

@Module({
  imports: [UsersModule], // サービス単体ではなくモジュールごと取り込む
  providers: [OrdersService],
})
export class OrdersModule {}

3公開しない内部サービス

exports に何を載せないかという設計判断の例です。パスワードハッシュ化のような内部実装は providers だけに留めることで、他の機能が内部実装へ依存する事故を防げます。exports はモジュールの公開 API そのものです。

import { Module } from "@nestjs/common";
import { UsersService } from "./users.service";
import { PasswordHashService } from "./password-hash.service";

@Module({
  // PasswordHashService は users 機能の内部実装
  providers: [UsersService, PasswordHashService],
  exports: [UsersService], // 外に見せるのは UsersService だけ
})
export class UsersModule {}

// exports を絞る = 「他機能はここからだけ触ってよい」という宣言
// 内部実装を後から自由に作り変えられる余地を残せる

4つながらないときの3点チェック

モジュールをまたぐ注入が解決できないときの実務チェックリストです。提供側の providers・exports、利用側の imports の3点がつながって初めて成立するため、エラーが出たらこの順で確認します。

// OrdersService に UsersService を注入しようとして
// Nest can't resolve dependencies of the OrdersService (?)
// と言われたときの確認手順:
//
// ① 提供側 UsersModule の providers に UsersService があるか
// ② 提供側 UsersModule の exports に UsersService があるか ← 最頻出
// ③ 利用側 OrdersModule の imports に UsersModule があるか
import { Module } from "@nestjs/common";
import { UsersService } from "./users.service";

@Module({
  providers: [UsersService], // ①
  exports: [UsersService],   // ② ここの足し忘れが一番多い
})
export class UsersModule {}

5共有モジュールを切り出す

多くの機能から使われるロガーを専用モジュールに切り出した実務例です。横断的な部品は「providers と exports に載せた小さなモジュール」として独立させ、使いたい側が imports する形が共有の定番パターンです。

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

@Injectable()
export class AppLoggerService {
  log(context: string, message: string): void {
    console.log("[" + context + "] " + message);
  }
}

// 多くの機能から使われる横断部品は、専用モジュールに切り出す
@Module({
  providers: [AppLoggerService],
  exports: [AppLoggerService],
})
export class LoggerModule {}

// 利用側は imports: [LoggerModule] と書くだけで
// どの機能からも AppLoggerService を注入できるようになる