티스토리 뷰

 

NestJS에 Exception filters를 추가합니다.

 

 

Documentation | NestJS - A progressive Node.js framework

Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Rea

docs.nestjs.com

 

 


 

서론

애플리케이션에서 발생하는 에러는 관련 정보를 포함하도록 정의하는 것이 좋습니다.

그래야 어떤 요청에서 어떤 에러가 나타났는지 정확히 확인하고 그에 맞는 대응을 할 수 있을테니까요.

 

NestJS Exception 

NestJS에서는 기본적으로  내장된 전역 예외 필터를 제공하고 있습니다. @nestjs/common 패키지를 통해 사용할 수 있죠.

전역 예외 필터는 HttpException, 즉 애플리케이션 레벨에서 발생하는 에러를 다룹니다.

 

아래와 같은 에러를 작성하고 호출하면,

import { ForbiddenException } from '@nestjs/common';

async throwException() {
  throw new ForbiddenException();
}

 

 

다음과 같은 응답을 받을 수 있습니다.

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

 

 

인식할 수 없는 에러(HttpException도 아니고, HttpException를 상속받지도 않은 에러)의 경우에는 `Internal server error(500)`로 표현됩니다.

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

 

 

 

기존에 지원되는 메시지만으로는 표현할 수 없는 다양한 예외 상황이 존재할 수 있겠죠?

때문에 커스텀도 지원하고 있습니다.

 

다음과 같이 HttpException 클래스를 이용해 원하는 메세지와 상태코드로 에러를 날리는 것이 가능합니다.

이를 이용하면 본인의 비지니스 로직에 맞는 상태코드와 메시지를 정의해서 사용할 수도 있습니다.  

async throwException() {
  throw new HttpException('Api is not found', 404);
}

 

{
  "statusCode": 404,
  "message": "Api is not found"
}

 

 

하지만 각 예외 상황마다 일일히 메세지나 상태코드를 작성하는건 번거로운 일입니다.

그리고 상태코드나 메시지만으론 에러가 발생 이유를 한눈에 파악하기가 힘들 것 같아요.

 

앞서 언급했다 싶이 에러는 많은 정보를 가지고 있는게 좋으니까요.

 

재사용성도 높이고, 다른 정보도 나타내도록 메시지를 커스텀 할 수 있으면 좋을것 같습니다. 🤔

 


 

Exception을 별도의 파일로 분리하기

재사용성을 높이기 위한 간단한 방법은 별도의 파일, 클래스로 분리한 뒤 import해 사용하는 것입니다.

 

앞서 작성한 오류를 별도의 파일 `test.exception.ts`으로 분리하겠습니다.

//test.exception.ts
import { HttpException, HttpStatus } from '@nestjs/common';

export class TestNotFoundException extends HttpException {
  constructor() {
    super('Api is not found', HttpStatus.NOT_FOUND);
  }
}

 

HttpException 클래스를 상속받아서 생성자에 메시지와 사용할 상태코드를 명시해주었습니다.

 

 

 

이제 로직에 클래스를 선언해주기만 하면 커스텀한 메시지와 상태코드가 담긴 에러를 사용할 수 있습니다.

 

import { TestNotFoundException } from 'src/exceptions/test.exception';

async throwException() {
  throw new TestNotFoundException();
}

 

 

 

Exception filters

NestJS에서는 ExceptionFilter클래스를 상속받아 전역 예외 필터를 확장할 수 있도록 지원하고 있습니다. 

이를 이용해 응답결과를 커스텀 하는것도 가능합니다.

 

에러 응답에 경로(path)와 발생 시간(timestamp)를 추가해보겠습니다.

 

`http-exception.filter.ts`를 아래와 같이 작성합니다.

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

@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,
      message: exception.message,
      path: request.url,
      timestamp: new Date().toISOString(),
    });
  }
}

 

에러 발생 시 정의된 json이 반환됩니다. 에러에 정의된 상태코드와 메세지는 그대로 사용하고, 경로에는 요청이 발생한 url이, 발생시간은 Date 클래스를 통해 생성된 시간 문자열이 입력됩니다.

 

 

 

필터를 전역적으로 적용하기 위해 `main.ts`에 `app.useGlobalFilter`를 정의한 `HttpExceptionFilter`로 지정합니다.

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const port = 3000;

  app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
  app.useGlobalFilters(new HttpExceptionFilter());

  await app.listen(port);
}
bootstrap();

 

 

이제 오류 발생시 아래와 같이 경로와 발생시간이 함께 출력됩니다. 이러한 형식은 로깅(logging)과 디버깅에도 유용합니다. ⭐

{
  "statusCode": 404,
  "message": "Api is not found"
  "path": "/test",
  "timestamp": "2024-03-20T12:34:42.441Z"
}

 

 

 


 

 

class-validator 메세지가 나타나지 않는 경우

class-validator로 dto의 유효성 검사를 하는 경우,  위의 exception filter를 적용하면 기존의 메시지가 나타나지 않고`Internal server error(500)`로 나타나게 됩니다. 

 

 

NestJS Exception filter messes up error array if it comes from ValidationPipe

So I use the ValidationPipe to validate my DTOs in NestJS, like this: // auth.dto.ts export class AuthDto { @IsEmail() @IsNotEmpty() email: string; } Without the Exception filter the error m...

stackoverflow.com

 

스택오버플로우에서 저와 같은 문제점을 겪는 글을 발견했습니다.

댓글에는 private로 설정되어 있으니 `exception["response"]["message"]`으로 우회해 접근하라고 나오라고 되어있네요.

 

 

음...일단은 구조를 좀 더 파악해 봅시다.

 

exception에 getResponse() 메소드가 정의 되어있어 이를 이용해 response를 가져와 보았습니다.

let errorMessage = exception.getResponse();

 

errorMessage의 타입이 `string | object` 인것을 보니 message가 단일로 넘어올때도 있고,

다른 부가 정보와 함께 객체로 넘어오는 경우도 있는것 같습니다.

 

실제 출력값은 아래와 같습니다.

{
  "statusCode": 404,
  "message": {
  	"message": [
  		"name should no to be empty",
    	"name must be a string value"
  	],
  	"error" : "Bad Request"
  	"statusCode": 400
   },
  "path": "/test",
  "timestamp": "2024-03-20T12:34:42.441Z"
}

 

 

class-validator 자체 응답폼인 것 같습니다. message가 배열로 넘어오고 있었네요.

간단하게 obj 인지만 확인해 메세지를 꺼내도록 수정해주었습니다.

 

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

@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();
    
    let errorMessage: any = exception.getResponse();
    if (typeof errorMessage === 'object') {
      errorMessage = errorMessage.message;
    }

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

 

 

이제 class-validator의 에러메시지가 fillter를 통과하더라도 잘 나타나게 됩니다.

 

 

감사합니다.

 


 

공부한 내용을 복습/기록하기 위해 작성한 글이므로 내용에 오류가 있을 수 있습니다.

댓글
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Total
Today
Yesterday