개발스토리

Nest - Exception filters 본문

Nestjs

Nest - Exception filters

무루뭉 2021. 7. 19. 22:01

 

Nest 공식 문서를 바탕으로 정리한 게시글입니다.

 


 

Nest에는 애플리케이션 전체에서 처리되지 않은 모든 예외를 처리하는 예외 레이어가 내장되어 있다. 코드에서 예외를 처리하지 않으면 이 레이어에서 예외를 포착하여 적절한 응답을 자동으로 보낸다.

 

기본적으로 이 작업은 HttpException 유형의 예외를 처리하는 내장 전역 예외필터에 의해 수행된다. 예외가 인식되지 않는다면 내장된 예외필터가 다음과 같은 기본 JSON 응답을 생성한다.

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

 

Throwing standard exceptions

 

Nest는 @nestjs/common 패키지에 내장 HttpException 클래스를 제공한다. 특정 오류 조건이 발생할 때 표준 HTTP 응답 객체를 보내는 것이 좋다.

 

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

HttpStatus는 @nestjs/common에서 가져온 헬퍼 enum형이다.

응답은 다음과 같다.

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

 

HttpException 생성자는 응답을 결정하는 두 개의 필수인수가 사용한다.

1. response 인수는 JSON을 정의한다.

2. status 인수는 HTTP 상태코드를 정의한다.

 

기본적으로 JSON 응답 본문에는 두 가지 속성이 포함된다.

1. statusCode: status 인수에 제공된 HTTP 상태코드가 기본값이다.

2. message: status에 따른 HTTP 오류에 대한 간단 설명

 

메시지 부분만 재정의하고 싶으면 response 인수에 문자열을 제공하면 된다. 전체적인 JSON 응답을 재정의하려면 response 인수에 객체를 전달하면된다. 아래 참조하면 된다.

 

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

응답은 다음과 같다.

{
  "status": 403,
  "error": "This is a custom message"
}

 

Custom exceptions

 

대부분에 경우에는 커스텀 예외를 작성할 필요가 없이 위에서 설명한대로 Nest HTTP 예외를 사용할 수 있다.

커스텀 예외를 만들어야하는 경우에는 커스텀 예외가 기본 HttpException 클래스에서 상속되는 고유한 예외 계층을 만드는 것이 좋다. 이러한 접근 방식을 사용하면 Nest가 예외를 인식하고 오류 응답을 자동으로 처리한다.

// forbidden.exception.ts

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

 

ForbiddenException은 기본 HttpException을 확장하므로 내장된 예외 핸들러와 원활하게 작동한다.

 

// cats.controller.ts

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

 

Built-in HTTP exceptions

 

HttpException에서 상속되는 표준 예외 집합을 제공한다. @nestjs/common 패키지에서 노출되며 일반적인 HTTP 예외 중 대부분이 있다.

해당하는 예외에 알맞게 작성하면 된다.

 

Exception filters

 

기본 예외필터가 많은 경우를 처리할 수 있지만 예외 레이어에 대한 완전한 제어를 원할 수도 있다. 예를들어, 로깅을 추가하거나 동적 요인을 기반으로 다른 JSON 스키마를 사용할 수도 있다. 예외 필터는 이러한 목적을 위해 설계되었다. 

HttpException 클래스의 예외를 포착하고 이에 대한 커스텀 응답 로직을 구현하는 예외 필터를 만들어보자. 이렇게 하려면 Request 및 Response에 액세스해야 한다. Request에 액세스하여 원래 url을 추출하여 로깅 정보에 포함할 수 있다. Response를 사용하여 response.json() 메서드를 사용하여 전송되는 응답을 직접 제어할 수 있다.

 

// 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<T> 인터페이스를 구현해야 한다. 이를 위해서 catch(exception: T, host: ArgumentHost) 메서드를 제공해야 한다. T는 예외 타입을 나타낸다.

 

@Catch(HttpException) 데코레이터는 필요한 메타데이터를 예외필터에 바인딩하여 이 특정필터가 HttpException 타입의 예외만 찾고있다는 것을 Nest에 알린다.

 

Binding filters

 

새로운 HttpExceptionFilter를 컨트롤러에 연결해보자.

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

 

위 코드는 인스턴스를 생성했다. 위 방법 대신 클래스를 전달하여 종속성 주입을 할 수 있다.

 

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

 

가능하면 인스턴스 대신 클래스를 사용하여 필터를 적용하는 것이 좋다. Nest는 전체 모듈에서 동일한 클래스의 인스턴스를 쉽게 재사용할 수 있어 메모리 사용량을 줄이기 때문이다.

 

위의 예들은 단일 create() 라우트 핸들러에만 적용되어 메서드 범위가 된다. 예외필터는 메서드 범위, 컨트롤러 범위, 전역 범위 등 다양한 수준으로 범위를 지정할 수 있다.

컨틀롤러 범위로 설정하려면 다음과 같이 작성한다. 컨트롤러 모든 라우트 핸들러에 대해 예외필터를 설정한다.

@UseFilters(new HttpExceptionFilter())
export class CatsController {}

 

전역범위 필터를 만들려면 다음을 수행한다. 모든 컨트롤러 및 모든 라우트 핸들러에 사용된다.

// main.ts

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

 

Catch everything

 

예외 유형에 관계없이 처리되지 않은 모든 예외를 잡으려면 @Catch() 데코레이터의 매개변수 목록을 비워둔다.

타입에 관계없이 발생한 예외를 포착한다.

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

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

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

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

 

 

'Nestjs' 카테고리의 다른 글

Nest - Controllers  (0) 2021.07.19
Nest - Hot Reload with CLI  (0) 2021.06.28
Nest - First steps  (1) 2021.06.25
Nest - Introduction  (0) 2021.06.25
Comments