NestJSを用いAPI作成をしているのだが、Responseを共通化したいと思ったので、実装してみた。

Responseの定石

とは言えそもそもResponseの形をどのようにするのがいいのかわからなかったので調べた結果、omniti-labs/jsendが参考になりそうだった。 これを参考に以下のような型で返すことにした。

export interface Response {
  status: 'success' | 'error';
  data: any;
  message: string[] | null;
}

ValidationPipeを使用する場合、エラーレスポンスの形式はBadRequestExceptionがデフォルト

たぶんドキュメントにある通り(Documentation | NestJS - A progressive Node.js framework)なので、ほとんどの場合ValidationPipeを使用することになる。 そうするとエラーレスポンスの形式はBadRequestExceptionがデフォルトになるので、以下のように返ってくる。

{
  "statusCode": 400,
  "message": "Bad Request Exception",
  "error": "Bad Request"
}

これは意図した形ではないのでカスタマイズする必要がある。

http-exception.filter.tsを作成する

カスタマイズするにはそれ用のException filtersを用意する必要がある。 Exception filtersとは、

Nestには例外処理レイヤーが組み込まれており、アプリケーション全体で処理されない例外をすべて処理する責任を負っています。アプリケーションのコードで処理できない例外は、このレイヤーでキャッチされ、適切なユーザーフレンドリーな応答が自動的に送信されます。

というものらしい。

とにかく例外フィルターは、NestJSアプリケーションが例外をキャッチした場合に呼び出されるらしい。

実装は簡単

まずはhttp-exception.filter.tsを作成する。

import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
} from '@nestjs/common';
import { Response } from 'express';

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const status = exception.getStatus();

    response.status(status).json({
      status: 'error',
      data: null,
      message: (exception.getResponse() as any).message || [
        '何らかのエラーが発生しました。',
      ],
    });
  }
}

それをmain.tsで読み込むだけで、

   app.useGlobalPipes(new ValidationPipe());
+  app.useGlobalFilters(new HttpExceptionFilter());

エラーレスポンスが期待した形になって返却されるようになった。

余談

これを実装するときに脳死でChatGPTに質問したところ、いくら質問してもうまく実装できず、ドキュメントを見たら即解決した。 馬鹿と鋏は使いようということで。