🐾
Nest Commanderを試してみる
はじめに
- NestJSでコンソールアプリケーションを簡単に作成できるNest Commanderを今回は試してみたいと思います。
対象読者
- NestJSでコンソールアプリケーションを効率的に書きたい方
Nest Commanderとは
-
@Command
の単位でコンソールアプリケーションを実装出来ます。 - NestJSのDIやログ機能がそのまま使えます。
-
@Command
内容を元にコマンドラインでの実行時に引数を指定することで実行が出来ます。 - 複数の
@Command
を一つのコンソールアプリケーションで管理できます。起動時にコマンドライン引数で実行する@Command
を指定出来ます。 - コマンドライン引数で変数を渡すときの変換処理は
@Option
で定義して実装できます。 - NestJS公式のStandalone applicationsページでNestJSを使用したコンソールアプリケーションの書き方が記載されています。この方法でやる場合、複数のコンソールアプリケーションを管理したい場合はmonorepo化が必要になります。Nest Commanderの場合はmonorepo化の必要はありません。
動作環境
- Node.js - 20.x
- Yarn - 1.22.x
使用ライブラリ
- nestjs - 10.x
- nest-commander - 3.15.x
作業方針
- コンソールログを出力するだけの2つの
@Command
を作成する - 1つの
@Command
にはコマンドライン引数で変数を与えてそれを変換してみる - 実行時にコマンドライン引数で切り替え出来るか確認する
サンプルコード
src/sample1.command.ts
import { Logger } from '@nestjs/common';
import { CommandRunner, Command, Option } from 'nest-commander';
/** コマンドライン引数で渡す変数の型管理 */
type CommandOptions = {
id?: number;
};
@Command({
// コマンドライン引数で`sample1`を指定することで起動可能
name: 'sample1',
// isDefault=true: コマンドライン引数を指定しない場合に起動できる。アプリケーション内で1つのみ指定可能
options: { isDefault: true },
})
export class Sample1Command extends CommandRunner {
/**
* --idの数字変換処理
* --id=数字を指定するとrunのoptions.idに値が設定される
*/
@Option({
flags: '--id [number]',
})
parseId(value: string): number {
return Number(value);
}
/**
* @Command.nameで呼び出された時に実行される処理
*/
async run(passedParams: string[], options?: CommandOptions): Promise<void> {
Logger.log({ name: Sample1Command.name, passedParams, options });
return Promise.resolve();
}
}
src/sample2.command.ts
import { Logger } from '@nestjs/common';
import { CommandRunner, Command } from 'nest-commander';
@Command({
name: 'sample2',
})
export class Sample2Command extends CommandRunner {
/**
* @Command.nameで呼び出された時に実行される処理
*/
async run(passedParams: string[], options?: Record<string, unknown>): Promise<void> {
Logger.log({ name: Sample2Command.name, passedParams, options });
return Promise.resolve();
}
}
src/app.module.ts
import { Module } from '@nestjs/common';
import { Sample1Command } from './sample1.command';
import { Sample2Command } from './sample2.command';
@Module({
imports: [],
// Sample1Command, Sample2Commandを指定する
providers: [Sample1Command, Sample2Command],
})
export class AppModule {}
src/main.ts
import { CommandFactory } from 'nest-commander';
import { AppModule } from './app.module';
async function bootstrap() {
// nest-commanderのCommandFactoryにAppModuleを指定する
// 出力されるログレベルを指定する(標準だとログ出力されないため)
await CommandFactory.run(AppModule, ['warn', 'error', 'debug', 'log']);
}
bootstrap();
動作確認
# ビルド
yarn build
# Sample1Command実行(デフォルトCommand)
node dist/main --id=123
node dist/main sample1 --id=123
# Sample2Command実行
node dist/main sample2
ソースコード一式
おわりに
- NestJSの基本機能を使えて、そこに複数のコンソールアプリケーションを実装できるのでNestJSに慣れている人には便利なライブラリだと感じました。
- DIやログ機能などがそのまま使えるのが特に良いです。
- コマンドライン引数の変換処理を宣言的に出来るので便利です。
- Node.jsで一からコンソールアプリケーションを作るよりも、型やレイヤーがしっかりしているのでソースコードの治安が一定水準に保てると思いました。
- Sub Commandsも作成できるため、CLIっぽいものを作りたい時に試してみたいと思います。
- 新規にバッチ処理を構築する時に導入を検討してみてはいかがでしょうか。
参考URL
Discussion
いきなりコメントすみません!
implements ではなく extends だと思われます!
コメントありがとうございます!
Nest Commanderが2 -> 3にバージョンアップ時に
interface
からabstract class
に変更があったようですね。記事とサンプルソースを修正しておきました。
あ、そうだったのですね ... !
経緯を把握せずにコメントしてしまいすみません 😅
執筆いただいた内容とても役に立ちました!
ありがとうございます🙏