🐙

6/24 NestJS 例外処理・例外Fileter振り返り

2024/06/25に公開

こんにちは。豆太郎です。
アルバイトで、NestJSの開発に取り組んでいます。
まだ、NestJSに取り組み始めて4か月程度なのですが、なかなか理解が進まず、社員の方の話を理解するのに苦労しています。理解が進まない理由の一つとしては、振り返りを行っておらず、一つの作業をして身につけた知識が記憶が定着をしないということがあげられます。具体的には、私はService.tsとmodule.tsの違いについて忘れていました(笑)。ですので、今回は学んだ知識を振り返りつつ、NestJSに関する理解を深めるための記事を書きたいと思います。

この記事は、私自身が理解するために作成された、日記のようなものです。いずれは、皆さんに読んでもらうほど、質の高い記事を出したいと思っていますが、今の時点では雑多な記事だと思ってください。

参考:NestJS公式ドキュメント

NestJSの例外処理について

  • 例外処理は、HttpExceptionを継承することでApplicationExceptionやBadRequestExceptionなど、HTTP statusに応じたの複数の例外処理を作成することができる。
badrequest.exception.ts
export class BadRequestException extends HttpException {
    constructor() {
        super('BadRequest', HttpStatus.BadRequest);
    }
}

例外処理の使われ方

○○.controller.ts
@Get()
async findAll() {
  try {
    await this.service.findAll()
  } catch (error) {
    throw new HttpException({
      status: HttpStatus.FORBIDDEN,
      error: 'This is a custom message',
    }, HttpStatus.FORBIDDEN, {
      cause: error
    });
  }
}

また上記のように、findAll関数の中でtryを使用して、処理に成功した場合に、findAllを実行して、失敗した場合にthorw new ”例外処理”を呼び出すケースが多い。

さらに、例外処理をthrow newする際に、messageや、causeなどの引数をつけることで、レスポンスにその引数の結果を返すことができる。

throw new BadRequestException('Something bad happened', { cause: new Error(), description: 'Some error description' })

上記の結果

{
  "message": "Something bad happened",
  "error": "Some error description",
  "statusCode": 400,
}

このように引数としてメッセージを付け加えることのメリットとしては、その例外処理が何かを原因を詳しく知ることに役立つことがあげられる。例えば、登録画面のフォームにおいての入力が誤っており、Nest側から(バックエンド)でBad Requestが発生した場合に、そのBad Requestのmessageの引数に”登録フォームの入力が不正です”といったメッセージを記載しておけば、エラーが生じた際に、例外処理の原因を突き止めることができる。

Exceptionfilterとは?

Exceptionfilterとは、いくかの例外処理に関して、クライアント側に返す処理をまとめて管理することのできるインスタンスである。例えば、ExceptionFilterは、クライアント側に返すレスポンスに関して、メッセージ・例外が発生した時間・エラーコードなどを自由に変更することができる。

http-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Request, 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 request = ctx.getRequest<Request>();
    const status = exception.getStatus();

    response
      .status(status)
      .json({
        statusCode: status,
        timestamp: new Date().toISOString(),
        path: request.url,
      });
  }
}

上記はExceptionFilterである。上記のコードでは、HttpExcptionExceptionFilterが実装されている。ここでは、@Catch(HttpException)のように、HttpExceptionが引数として受け取ることができる。ただし、@Catch()のように引数が無い場合はすべての例外処理を引数として受け取ることができる。

また、以下のようにControllerにて、HttpExceptionFilterをcreate関数に関連付ける(バインディング)することができる。@UseFiltersは@Catchと処理が似ていて、引数としてFilterのインスタンスを受け取る。

cats.controller.ts
@Post()
@UseFilters(new HttpExceptionFilter())
async create(@Body() createCatDto: CreateCatDto) {
  throw new ForbiddenException();
}

上記のコードでは、HttpExceptionFilterが単一のメソッドに対してスコープされるが、controller/resolver/gatewayなどより広い範囲に対しても、UseFiltersを使って、スコープすることができる。

例えば以下では、グローバススコープとしてHttpExceptionFilterを定義している。

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

上記を定義することで、すべてのNestJSのファイルで記述されている例外処理(HttpException)に関して、HttpExceptionFilterがバインディングされる。
単一のメソッドに適応するのではなく、グローバルスコープを、広い範囲に対してExceptionFilterを定義するメリットとしては、各例外処理において、UseFilterを定義する必要が手間が省けることがあげられる。

Discussion