Oteto Blogのロゴ

Next.jsでDI (Dependency Injection) する with TSyringe

Next.js 14でDI (Constructor Injection) する方法を模索していたところ、TSyringeでお手軽に実現できそうだったので実際に試してみる。

1. ライブラリのインストール

npm i tsyringe reflect-metadata

2. デコレータの有効化

tsconfig.json{
  ...
  "compilerOptions": {
    ...
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

3. DIコンテナの初期化とプロバイダの登録

touch di.ts
src/di.tsimport 'reflect-metadata';
import { container } from 'tsyringe';
import { UserRepository } from '../user.repository';

type RegisterParams = Parameters<typeof container.register>;
type Provider = {
  token: RegisterParams[0];
  provider: RegisterParams[1];
};

const providers: Provider[] = [
  {
    token: 'UserRepository',
    provider: UserRepository,
  },
];

const DiContainer = container;
providers.forEach(({ token, provider }) => {
  container.register(token, {
    useClass: provider,
  });
});

export { DiContainer };

reflect-metadataをimportしつつ、DIコンテナを初期化し注入したいプロバイダを登録する。

ちなみに基本複数登録することになるかと思うので配列としてプロバイダ達を定義している。

4. Instrumentationの利用

サーバ起動時にDIコンテナの初期化・クラスの登録をするべく、Next.jsのInstrumentation (ver14時点だとexperimental) を利用する。

next.config.js/** @type {import('next').NextConfig} */
const nextConfig = {
    experimental: {
      instrumentationHook: true
    }
};
export default nextConfig;

まずinstrumentationHookを有効化する。

touch instrumentation.ts
src/instrumentation.tsexport async function register(): Promise<void> {
  await import('./di');
}

その後プロジェクト直下に作成したinstrumentation.tsで先ほどのdi.tsをimportする。

5. インスタンスの注入

下準備が完了したので、とあるクラスに先ほどプロバイダとして登録したクラスのインスタンスを注入してみる。

import type { IUserRepository } from '../user.repository.interface';
import { inject, injectable } from 'tsyringe';

@injectable()
export class GetUserUsecase {
  constructor(
    @inject('UserRepository') private readonly userRepository: IUserRepository,
  ) {}

  handle(): void {
    this.userRepository.someMethod();
  }
}

これで無事動きuserRepositoryのメソッドも呼び出せた。

今回はシンプルなConstructor Injectionしかしてないがとりあえず目的は達成できた。