たかぎとねこの忘備録

プログラミングに関する忘備録を自分用に残しときます。マサカリ怖い。

NestJSでパイプをつくる話

パイプは、@Injectable()デコレーターでアノテーションされていてかつPipeTransformインターフェースを実装しているクラスのことである。

docs.nestjs.com

用途は、コントローラーのルートハンドラで、@Query()@Param()などで受け取った入力データを希望の型やフォーマットに変換するために使われる。

あとは、その受け取った入力データがこのメソッドの入力データとして正しいかどうかなどを検証するために使われる。正しくない場合は例外をスローする。

個人的に便利なのが、ルートメソッドで受け取る入力データは基本的に文字列なので、それをDate型などに変換したりすることで、ルートメソッドで変換処理を実装しなくて済むようになるところ。

実際に作ってみる。パイプ名は-で単語間を結ぶようにする。語尾に*-pipeをつけなくて良い。自動的に付与される。

作成されるファイル名は*.pipe.tsになる。

nest g pipe parse-date

srcディレクトリに、parse-date.pipe.tsとparse-date.pipe.spec.tsファイルが作成される。

transformメソッドに処理内容を実装していく。

ParseTransformの第一型引数には受け取る入力データの型を指定する。 第二型引数にはtransformメソッドの返り値の型を指定する。

transformメソッドの引数のvalueはデフォルトでany型なので、具体的な型を指定しておくのもいいと思う。返り値の型も同様。

// src/pipes/parse-date.pipe.ts

import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common';

@Injectable()
export class ParseDatePipe
  implements PipeTransform<string | undefined, Date | undefined>
{
  transform(
    value: string | undefined,
    metadata: ArgumentMetadata,
  ): Date | undefined {
    if (!value) {
      return undefined;
    }
    if (typeof value !== 'string') {
      return undefined;
    }

    try {
      const time = parseInt(value, 10);
      return new Date(time);
    } catch (e: unknown) {
      if (e instanceof Error) {
        console.log(`e: ${e.message}`);
      }
      return undefined;
    }
  }
}

テストも一緒に書いておく。

// src/pipes/parse-date.pipe.spec.ts

import { ParseDatePipe } from './parse-date.pipe';

describe('ParseDatePipe', () => {
  it('should be defined', () => {
    expect(new ParseDatePipe()).toBeDefined();
  });

  it('ミリ秒を表す文字列を渡すとDate型に変換する', () => {
    const pipe = new ParseDatePipe();
    const baseDate = new Date().getTime();
    const result = pipe.transform(`${baseDate}`, {} as any);
    expect(result).toBeDefined();
    expect(result.getTime()).toBe(baseDate);
  });
  it('undefinedを渡すとエラーの代わりにundefinedを返す', () => {
    const pipe = new ParseDatePipe();
    const result = pipe.transform(undefined, {} as any);
    expect(result).toBeUndefined();
  });
});

使うときはルートメソッドの引数に指定したデコレーターの第二引数にクラスをそのまま渡すだけ。

@Get()
async findAll(
  @Req() request,
  @Query('base_date', ParseDatePipe) baseDate: Date | undefined
) {}
...