😸

Nest.js でサーバ起動時にレコードを dump する

2024/02/07に公開

背景

Cykinso の 研究&データ技術開発 の山﨑です。

Nest.js で作成している API サーバを本番運用で利用する時にあらかじめレコードを登録している必要がありました。
そこで MySQL を直接叩いて dump するのもいいですが、 Nest.js の機能を利用して一括登録する方法がないか調べました。

方法

Nest.js ではサーバ起動時に特定の method を実行することができます。
そこで、データを登録する initializeData method を実装し、 initializeData method をサーバの起動時に実行するようにしました。

Service

例えば Product という module を作成したいとします。

あらかじめ作成した JSON を読み込み Product object としてデータベースに登録する initializeData method を以下のように product.service.ts に追加します。

product.service.ts
import { Injectable, NotFoundException } from '@nestjs/common'

// ...skipping

@Injectable()
export default class ProductService {
  constructor(
    @InjectModel('Product')
    private readonly model: Model<Product, ProductKey>,
  ) {}

  // ...skipping

+   async initializeData(): Promise<void> {
+     if ((await this.products()).length > 0) {
+       return
+     }
+ 
+     const product = ProductJson as any
+     await this.create(product)
+   }
}

initializeData method はサーバが起動するたびに実行されるようにしたいのですが、開発中は何度もサーバを再起動することがあり、その度に同じレコードを登録しようとすると Product テーブルに適応している Unique 制約でレコードが登録できずエラーを吐いて強制終了してしまいます。

そこで、 (await this.products()).length > 0 として、すでにレコードが登録されている場合は何もしないようにしました。

Module

続けて Product.module.ts をアップデートします。

product.module.ts
- import { Module } from '@nestjs/common'
+ import { Logger, Module, OnModuleInit } from '@nestjs/common'
+ import { ConfigModule, ConfigService } from '@nestjs/config'
import SurveyItemController from './survey-item.controller'
import SurveyItemService from './survey-item.service'

+ const logger = new Logger('ProductModule')

@Module({
-   imports: [],
+   imports: [ConfigModule.forRoot()],
  controllers: [ProductController],
  providers: [ProductService],
  exports: [ProductService],
})
- export default class ProductModule {}
+ export default class ProductModule implements OnModuleInit {
+   constructor(
+     private readonly productService: ProductService,
+     private readonly configService: ConfigService,
+   ) {}
+
+   async onModuleInit() {
+     const stage = this.configService.get<string>('STAGE')
+     if (stage === 'test') {
+       return
+     }
+     await this.productService.initializeData()
+     logger.log("Product initialized")
+   }
+ }

ProductModuleOnModuleInit を継承するようにアップデートします。 続けて onModuleInit method を実装します onModuleInit method はサーバ起動時に必ず一度実行される method です。
この method で先ほど実装した initializeData method を実行します。

また、自動でレコードが登録される機能は今回はユニットテスト時には不要でしたので .envSTAGE を参照し test の場合はスキップするようにしました。

.env
STAGE="test"

実行

実行されるかサーバを起動して確認します。

npm run start:dev
[7:38:37 AM] Starting compilation in watch mode...
[7:39:29 AM] Found 0 errors. Watching for file changes.

[Nest] 70254  - 02/01/2024, 7:40:26 AM     LOG [NestFactory] Starting Nest application...
[Nest] 70254  - 02/01/2024, 7:40:26 AM     LOG [InstanceLoader] ConfigHostModule dependencies initialized +0ms
[Nest] 70254  - 02/01/2024, 7:40:26 AM     LOG [InstanceLoader] AppModule dependencies initialized +0ms
[Nest] 70254  - 02/01/2024, 7:40:26 AM     LOG [InstanceLoader] ConfigModule dependencies initialized +1ms
[Nest] 70254  - 02/01/2024, 7:40:26 AM     LOG [InstanceLoader] ConfigModule dependencies initialized +0ms
...skipping
[Nest] 70254  - 02/01/2024, 7:40:27 AM     LOG [NestApplication] Nest application successfully started +148ms
[Nest] 70254  - 02/01/2024, 7:40:27 AM     LOG [ProductModule] Product initialized

ログの末尾を見ると ProductModuleonModuleInit method が実行されているのがわかります。

💡 まとめ

  • サーバ利用時に必要なレコードを一括で登録できるようにしました
  • onModuleInit method を利用することでサーバ起動時にさせたい作業を決めることができます
  • test 時には実行しないなど環境で切り分けることもできました

参考

https://docs.nestjs.com/fundamentals/lifecycle-events

Cykinso's Tech Blog

Discussion