コンテンツにスキップ

NestJS で Interface を使った Dependency Injection を実現する

NestJS は文字列のトークンを実体クラスに置き換えて DI することができる。

これを使ってクラス間をインターフェースで結びつけることができる。

インターフェースの定義

以下UsersServiceというサービスをインターフェースで DI できるようにする例を記載する。

インターフェースを定義する。

src/users/interfaces/users.service.interface.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import { CreateUserDto } from "../dto/create-user.dto";
import { UserEntity } from "../entities/user.entity";

export interface IUsersService {
  /**
   * ユーザを取得する
   * @param id ID
   */
  findOne(id: string): Promise<UserEntity>;

  // 長いので省略
}

実装

export class UsersService implements IUsersServiceみたいな感じでインターフェースの実装を行う。

src/users/users.service.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import { Inject, Injectable } from "@nestjs/common";
import { TYPES } from "src/types";
import { IUsersRepository } from "./interfaces/users.repository.interface";
import { IUsersService } from "./interfaces/users.service.interface";
// 他のimportは省略

@Injectable()
export class UsersService implements IUsersService {
  constructor(
    @Inject(TYPES.UsersRepository)
    private readonly usersRepository: IUsersRepository
  ) {}

  async findOne(id: string): Promise<UserEntity> {
    // 実装する
    return await this.usersRepository.findOne(id);
  }

  // 長いので省略
}

文字列トークンの定義

インターフェースの分だけ定数を定義する。

src/types.ts
1
2
3
4
5
6
7
8
export const TYPES = {
  UsersRepository: Symbol.for("UsersRepository"),
  UsersService: Symbol.for("UsersService"),
  TodosRepository: Symbol.for("TodosRepository"),
  TodosService: Symbol.for("TodosService"),
  LoggerService: Symbol.for("LoggerService"),
  AuthService: Symbol.for("AuthService"),
};

モジュールの記載

以下のprovidersのように{provide: <文字列トークン>, useClass: 実体クラス}で記述する。

src/users/users.module.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { TYPES } from "src/types";
import { UsersRepository } from "./users.repository";
import { UsersService } from "./users.service";
// 他のimportは省略

@Module({
  // このモジュールが動くのに必要なモジュールを記述する。
  imports: [TypeOrmModule.forFeature([UserEntity])],
  // このモジュールが提供するコントローラを記述する。
  controllers: [UsersController],
  // このモジュール内で使うサービスを記述する。
  providers: [
    { provide: TYPES.UsersRepository, useClass: UsersRepository },
    { provide: TYPES.UsersService, useClass: UsersService },
  ],
  // このモジュールが他のモジュールへ提供するサービスを記述する。
  exports: [TYPES.UsersService],
})
export class UsersModule {}

DI

コンストラクタにて@Inject()を使って DI する。

src/users/users.controller.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import { Inject } from "@nestjs/common";
import { TYPES } from "src/types";
import { IUsersService } from "./interfaces/users.service.interface";
// 他のimportは省略

@Controller("users")
export class UsersController {
  constructor(
    @Inject(TYPES.UsersService) private readonly usersService: IUsersService
  ) {}

  // 省略
}