🐈‍⬛

NestJSでMongoやAxiosのエラーをハンドリングする方法

2023/01/05に公開

はじめに

NestJS で Mongo や Axios のエラーをハンドリングしたので、そのメモ。
詳しくは公式のドキュメント参照。
Exception filters | NestJS - A progressive Node.js framework

戦略

バックエンドでエラーが発生した時、フロントエンドにいい感じのエラーを返す必要がある。
try-catch して、エラー出た時にそれっぽいコードとメッセージを返したらいいんだけど、NestJS の機能を使っていい感じにしたい。

最初考えた作戦は、しかるべき条件で NestJS が準備してるHttpExceptionっていうErrorクラスをextendしたクラスを throw しちゃうやつ。
こんな感じ。
throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);

ちなみに、Nest では Built-in HTTP exceptions ってのがあって、こんな感じでもエラーを throw できちゃう。シンプル。
throw new ForbiddenException();

上記方法でも、そこそこいい感じにエラーをハンドリングできる。
ただ、Axios とか Mongo でエラーが発生した場合、エラーメッセージ等の情報を載せ替えたり、少しめんどくさいし、コードが読みにくくなりそう。
なので、NestJS の Exception Filter って機能を使って良い感じにハンドリングする。
実際にやってるのは、ExceptionFilterって呼ばれる interface を実装したクラスを準備して、指定したエラーが発生した時に NestJS に catch してもらって、良い感じの HTTP ステータスコードと json を返してもらうことにする。

Axios

Axios のエラーはAxiosErrorってクラスで throw されるので、それを下記のようなファイルを作成してキャッチした時にフロントエンドに返すステータスコードと json を指定する。Express から取ってきたResponseって Interface を使って response を作ってる。なので Express 的な書き方で返すものを決めてる。
今回、おまけでいくつかの変数をconsole.log()してる。

axios-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost } from '@nestjs/common';
import { AxiosError } from 'axios';
import { Request, Response } from 'express';

@Catch(AxiosError)
export class AxiosExceptionFilter implements ExceptionFilter {
  catch(exception: AxiosError, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getResponse<Request>();
    const status = exception.response.status;

    console.log({
      timestamp: new Date().toISOString(),
      status: status,
      statusText: exception.response.statusText,
      message: exception.response.data,
      path: request.url,
    });

    response.status(status).json({
      status: status,
      error: exception.response.statusText,
    });
  }
}

シンプルに、下記のようにしてグローバルに設定した。適用範囲に制限をかけたかったら他にもやり方あるので、公式ドキュメント参照。

main.ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new AxiosExceptionFilter());
  await app.listen(configuration.port);
}
bootstrap();

Mongo

Mongo のエラーはMongoErrorってクラスで throw されるので、それを下記のようなファイルを作成してキャッチした時にフロントエンドに返すステータスコードと json を指定する。Axios とほぼ同じ。

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

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

    console.log({
      timestamp: new Date().toISOString(),
      status: 400,
      message: exception.message,
    });

    let code = 400;
    if (exception.code === 11000) {
      code = 409;
    }
    response.status(code).json({
      status: code,
      error: exception.message,
    });
  }
}

Axios の時と同様こんな感じで適用する。

main.ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new AxiosExceptionFilter());
  app.useGlobalFilters(new MongoExceptionFilter());
  await app.listen(configuration.port);
}
bootstrap();

おわりに

NestJS の Exception filter を使うことで、良い感じに正常系と異常系を分けて書くことができた。良い感じ。
今回、シンプルに Express を使ったけど、似たような感じで Fastify を使ってもできるはず。
generics 使ったりしてる部分も勉強になった。
フレームワーク様様。

GitHubで編集を提案

Discussion