[NestJS] Exception filters

참고

  1. NestJS Exception filters

Exception filters 란

어플리케이션에서 발생한 예외를 처리하는 역할 Nest는 어플리케이션 전반에 걸쳐 예외를 처리하는 exceptions layer가 내장되어 있어, handle 되지 않은 예외까지 포착하여 적절한 response를 반환한다.

예를 들어, API를 호출하였는데 실행된 코드가 존재하지 않는 객체의 프로퍼티에 접근하였다. 해당 예외에 대해 아무런 처리를 하지 않았다면 아래와 같은 response를 받는다.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>TypeError: Cannot read properties of undefined ...</pre>
</body>
</html>

하지만 Nest의 경우 handle 되지 않은 예외까지 포착하기 때문에 아래와 같은 response를 받을 수 있다.

{
  "statusCode": 500,
  "message": "Internal server error"
}

사용법

기본 사용법

Throwing standard exceptions

예외 상황을 처리할 때 Nest에 내장된 ExceptionHttpStatus를 사용할 수 있다. 아래 예시는 Nest의 가장 기본적인 Exception인 HttpException이고 HttpStatus는 helper enum으로, 아래에서 쓰인 값은 403이다.

@Get()
async findAll() {
  throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}

호출결과는 아래와 같다.

{
  "statusCode": 403,
  "message": "Forbidden"
}

response의 statusCodemessage 프로퍼티는 Nest에서 디폴트로 지정되어 있는 포맷이고 만약 특정한 포맷으로 보낼 필요가 있다면 아래와 같이 전체 response body를 overriding 할 수 있다.

@Get()
async findAll() {
  throw new HttpException({
    status: HttpStatus.FORBIDDEN,
    error: 'This is a custom message',
    timestamp: new Date().toISOString(),
  }, HttpStatus.FORBIDDEN);
}

호출결과는 아래와 같다.

{
    "status": 403,
    "error": "This is a custom message",
    "timestamp": "2022-10-03T12:45:38.515Z"
}

Custom exceptions

위에 나온 Exception 외에 커스텀 Exception이 필요하다면 HttpException을 상속받은 클래스로 만들 수 있다. 이는 Nest가 해당 Exception을 인지하도록 하여 알맞은 error response를 보내도록 한다.

export class ForbiddenException extends HttpException {
  constructor() {
    super('Forbidden', HttpStatus.FORBIDDEN);
  }
}

아래와 같이 사용이 가능하다.

@Get()
async findAll() {
  throw new ForbiddenException();
}

Exception filters

Nest에 내장된 HttpExceptionHttpStatus를 이해하였다면 이제 Exception filter를 만들 차례이다. 모든 예외를 처리하는 Exception filter는 아래와 같은 코드로 만들 수 있다.

// all-exception.filter.ts

import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
  HttpStatus,
} from '@nestjs/common';
import { HttpAdapterHost } from '@nestjs/core';

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  constructor(private readonly httpAdapterHost: HttpAdapterHost) {}

  catch(exception: unknown, host: ArgumentsHost): void {
    // In certain situations `httpAdapter` might not be available in the
    // constructor method, thus we should resolve it here.
    const { httpAdapter } = this.httpAdapterHost;

    const ctx = host.switchToHttp();

    const httpStatus =
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;

    const responseBody = {
      statusCode: httpStatus,
      timestamp: new Date().toISOString(),
      path: httpAdapter.getRequestUrl(ctx.getRequest()),
    };

    httpAdapter.reply(ctx.getResponse(), responseBody, httpStatus);
  }
}

만든 exception filter를 컨트롤러에 적용하기 위해서는@UseFilters() 데코레이터를 사용한다.

// 하나의 API에 적용하는 경우
@Post()
@UseFilters(AllExceptionsFilter)
async create(@Body() createCatDto: CreateCatDto) {
  throw new ForbiddenException();
}

// 컨트롤러 전체에 적용하는 경우
@UseFilters(AllExceptionsFilter)
@Controller('trees')
export class TreesController {
...
}

links

social