😨

[TypeScript NestJS] 例外処理ベストプラクティス

に公開

こんにちは、ヨルシカふぁんです!
最近の実務ではNestJSを使ったAPI改修に取り組んでいます
その中でも 例外処理の統一や設計方針に悩む機会が多く、今回、自分なりにベストプラクティスを整理して記事にまとめることにしました!

グローバルなExceptionエラーを構築する

グローバルにExceptionエラーを構築しないと、毎回バラバラのエラーレスポンスの形式になってしまいイケてないですよね。NestJsだとグローバルに宣言できてかつ、エラーレスポンスが統一できる@Catchデコーダというものがあります。このデコーダを使ってExceptionFilterを継承したクラスを作成することができてカスタムフィルターを用意できます。

ExceptionFilterの実装例

たとえば、こんなふうにしてグローバルなエラーフィルターを定義できます:

all-exceptions.filter.ts
import {
  ArgumentsHost,
  BadRequestException,
  Catch,
  ExceptionFilter,
  HttpStatus,
} from '@nestjs/common';
import { ApiException } from './api-exception';
import { ApiStatusCode } from './api-info';
import { AbnormalError, SubnormalError } from './error';

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();

    const statusCode = this.resolveStatusCode(exception);
    const message = this.resolveMessage(exception);

    response.status(statusCode).json({ statusCode, message });
  }

  private resolveStatusCode(exception: unknown): number {
    const isBadRequest =
      exception instanceof BadRequestException ||
      exception instanceof SubnormalError ||
      (exception instanceof ApiException &&
        exception.getErrorCode() === ApiStatusCode.CONFLICT);

    return isBadRequest
      ? HttpStatus.BAD_REQUEST
      : HttpStatus.INTERNAL_SERVER_ERROR;
  }

  private resolveMessage(exception: unknown): string {
    if (exception instanceof BadRequestException) {
      const res = exception.getResponse();
      if (typeof res === 'string') return res;
      if (typeof res === 'object' && 'message' in res) {
        return Array.isArray(res.message) ? res.message[0] : res.message;
      }
      return exception.message;
    }

    if (exception instanceof Error) return exception.message;

    return 'Internal server error';
  }
}

このフィルターを main.ts に登録すれば、アプリ全体で統一されたエラーレスポンスが返るようになります!

main.ts
app.useGlobalFilters(new AllExceptionsFilter());

ログはDIして外部に出す

上記の実装例ではconsole.logconsole.errorも使っていません。
開発環境ではそれでも問題ありませんが、本番環境では必ずログを外部に出す設計にしましょう。
CloudWatch Logsなどに出力できるようにするのがベストですね!

constructor(private readonly logger: LoggerService) {}

おわりに

ExceptionFilterを活用すると、例外レスポンスの統一、ログ管理、設計方針の明確化につながっていいですね!
個人開発のコードでも実装したい、、!

参考

https://docs.nestjs.com/exception-filters

Discussion