🐾

Nest Commanderを試してみる

2022/03/26に公開
3

はじめに

  • NestJSでコンソールアプリケーションを簡単に作成できるNest Commanderを今回は試してみたいと思います。

対象読者

  • NestJSでコンソールアプリケーションを効率的に書きたい方

Nest Commanderとは

https://docs.nestjs.com/recipes/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

ソースコード一式

https://github.com/yasu-s/nest-commander-sample/tree/demo2

おわりに

  • NestJSの基本機能を使えて、そこに複数のコンソールアプリケーションを実装できるのでNestJSに慣れている人には便利なライブラリだと感じました。
  • DIやログ機能などがそのまま使えるのが特に良いです。
  • コマンドライン引数の変換処理を宣言的に出来るので便利です。
  • Node.jsで一からコンソールアプリケーションを作るよりも、型やレイヤーがしっかりしているのでソースコードの治安が一定水準に保てると思いました。
  • Sub Commandsも作成できるため、CLIっぽいものを作りたい時に試してみたいと思います。
  • 新規にバッチ処理を構築する時に導入を検討してみてはいかがでしょうか。

参考URL

https://docs.nestjs.com/recipes/nest-commander

https://nest-commander.jaymcdoniel.dev/

Discussion

nkmryunkmryu

いきなりコメントすみません!
implements ではなく extends だと思われます!

nkmryunkmryu

あ、そうだったのですね ... !
経緯を把握せずにコメントしてしまいすみません 😅

執筆いただいた内容とても役に立ちました!
ありがとうございます🙏