NestJSでRakeタスクみたいなことをしたい
概要
NestJSでRailsのRakeタスクのようなことをしたいと思ったので、その方法を調べてみた。
NestJSの公式ドキュメントではmain.tsにNest-Commanderを組み込む方法が紹介されているが、それをするとタスク実行専用のアプリケーションになってしまうので、main.tsにはWebサーバーとしての機能を果たしてもらいながら、同時に他の場所でタスクも実行できるようにしたい。
状況・目標設定
状況
WebアプリケーションとしてBooksControllerやBooksServiceが稼働しているとする。
目標
Nest-Commanderを使って、BooksServiceを利用したタスクをコマンドラインから実行できるようにする。
現在のアプリケーションコード
稼働中のWebアプリケーションのコードは以下のようになっているとする。
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { BooksModule } from './books/books.module';
@Module({
imports: [BooksModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
import { Module } from '@nestjs/common';
import { BooksController } from './books.controller';
import { BooksService } from './books.service';
@Module({
controllers: [BooksController],
providers: [BooksService],
})
export class BooksModule {}
import { Controller, Get } from '@nestjs/common';
import { BooksService } from './books.service';
@Controller('books')
export class BooksController {
constructor(private readonly booksService: BooksService) {}
@Get()
findAll() {
return this.booksService.getBooks();
}
}
import { Injectable } from '@nestjs/common';
@Injectable()
export class BooksService {
getBooks() {
return [
{ id: 1, title: 'Book 1', author: 'Alice' },
{ id: 2, title: 'Book 2', author: 'Bob' },
{ id: 3, title: 'Book 3', author: 'Carol' },
];
}
}
localhost:3000/books
にアクセスすると、レスポンスが返ってくることも確認できる。
流れ
1. Nest-Commanderをインストール
Nest-Commanderの公式ドキュメントに従い、Nest-Commanderをインストールする。
npm i nest-commander
2. Tasks Moduleを作成
タスクをまとめるためのTasksModuleを作成する。
nest g module tasks
タスクではBooksServiceを利用する予定なので、TasksModule内でBooksModuleをインポートしておく。また、BooksModule以外のModuleでもBooksServiceを利用できるように、BooksModuleでBooksServiceをエクスポートしておく。
import { Module } from '@nestjs/common';
import { BooksModule } from 'src/books/books.module';
@Module({
imports: [BooksModule],
})
export class TasksModule {}
import { Module } from '@nestjs/common';
import { BooksController } from './books.controller';
import { BooksService } from './books.service';
@Module({
controllers: [BooksController],
providers: [BooksService],
exports: [BooksService],
})
export class BooksModule {}
3. 適当にタスクを作成
今回はgetBooksの結果をコンソールに表示するタスクを作成する。
Nest-Commanderは独自のデコレータやクラスを持つので、nest g
コマンドを使って雛形を利用するより、一から手動でタスクを作成したほうが分かりやすいという印象。今回はタスクだということが分かりやすいようにtask.tsでファイル名が終わるようにした。
import { Command, CommandRunner } from 'nest-commander';
import { BooksService } from 'src/books/books.service';
@Command({ name: 'console-books', description: 'getBooksの中身を出力' })
export class ConsoleBooksCommand extends CommandRunner {
constructor(private readonly booksService: BooksService) {
super();
}
async run(): Promise<void> {
console.log(this.booksService.getBooks());
}
}
@Command({ name: 'console-books', description: 'getBooksの中身を出力' })
のnameの部分がタスクの名前になる。コマンドラインからタスクを呼び出すときもこの名前を利用する。
依存注入はNestJSのサービスと同様にconstructorで行う。ただし、通常のサービスと異なりconstructorの中でsuper
を呼び出す必要がある。
実際のタスクの処理内容はrun
メソッド内に記述する。
その他にもコマンドライン引数を取る機能などがある。詳しくはNest-Commanderの公式ドキュメントを参照。
4. TasksModuleに作成したタスクを登録
import { Module } from '@nestjs/common';
import { BooksModule } from 'src/books/books.module';
import { ConsoleBooksCommand } from './console-books.task';
@Module({
imports: [BooksModule],
providers: [ConsoleBooksCommand],
})
export class TasksModule {}
5. cli.tsを作成する
main.tsと同じ階層にcli.tsを作成する。
Webアプリケーションはmain.tsを起点に、タスク実行はcli.tsを起点に実行されるイメージ。
import { CommandFactory } from 'nest-commander';
import { TasksModule } from './tasks/tasks.module';
async function bootstrap() {
await CommandFactory.run(TasksModule, ['log', 'debug', 'warn', 'error']);
}
bootstrap();
['log', 'debug', 'warn', 'error']
の部分はログの出力レベルの設定なのでお好みに合わせてカスタマイズ。
6. ビルドして、タスクを実行
npm run build && node dist/cli.js console-books
[Nest] 95301 - 04/11/2024, 11:33:04 PM LOG [NestFactory] Starting Nest application...
[Nest] 95301 - 04/11/2024, 11:33:04 PM LOG [InstanceLoader] CommandRootModule dependencies initialized +8ms
[Nest] 95301 - 04/11/2024, 11:33:04 PM LOG [InstanceLoader] BooksModule dependencies initialized +0ms
[Nest] 95301 - 04/11/2024, 11:33:04 PM LOG [InstanceLoader] DiscoveryModule dependencies initialized +0ms
[Nest] 95301 - 04/11/2024, 11:33:04 PM LOG [InstanceLoader] TasksModule dependencies initialized +0ms
[Nest] 95301 - 04/11/2024, 11:33:04 PM LOG [InstanceLoader] CommandRunnerModule dependencies initialized +0ms
[
{ id: 1, title: 'Book 1', author: 'Alice' },
{ id: 2, title: 'Book 2', author: 'Bob' },
{ id: 3, title: 'Book 3', author: 'Carol' }
]
実行できました。めでたし。
Discussion