lottie
Seungjun's blog
blog
nestjs docs 3편

Handling errors

Exception filters

   Nest는 애플리케이션 전체에서 처리되지 않은 모든 예외를 처리하는 Exception filters(예외 레이어)가 기본적으로 있다. 어플리케이션에서 예외를 처리하지 않으면 기본적으로 제공되는  Exception filter(예외 레이어) 계층에서 자동으로 Exception을 포착하여 적절한 응답을 보낸다.


image


  기본적으로 이 작업은 유형 (및 하위 클래스) 의 예외를 처리하는 내장된 전역 예외 필터 에 의해 수행된다. HttpException가 인식되지 않는 경우 (그리고 HttpException에서 상속하는 클래스도 아닌 경우) 내장 Exception filter는 다음과 같은 기본 JSON 응답을 생성합니다.

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

  전역 Exception filter는 부분적으로 http-errors라이브러리를 지원합니다. statusCodemessage 를  포함하는 throw된 예외는 message가 변경되고 response로 전송됩니다.


( 기본 응답인 InternalServerErrorException 대신)


Throwing standard exceptions

  Nest는 내장HttpException 클래스를 제공한다. 일반적인 HTTP REST/GraphQL API 기반 애플리케이션의 경우 특정 오류 조건이 발생할 때 표준 HTTP 응답 객체를 보내는 것이 가장 좋습니다.

 예를 들어 에는 메소드( 라우트 핸들러) CatsController가 있습니다 . 이 경로 처리기가 어떤 이유로 예외를 throw한다고 가정해 보겠습니다.

// cats.controller.ts

@Get()
async findAll() {
  throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}
// 클라이언트가 받는 response 
{
  "statusCode": 403,
  "message": "Forbidden"
}

  생성자 HttpException는 request를 결정하는 두 개의 필수 인자를 사용한다.

  • response 인자는 JSON response body를 결정한다. 이는 string 또는object 일 수 있다.

    (위 코드에서 'Forbidden')

    • 기본적으로 JSON response body에는 두 가지 속성이 포함된다.

      • statusCode: 기본값은 status 에서 제공된 HTTP 상태 코드입니다.

      • message: HTTP 오류에 대한 간단한 설명

    • JSON response body의 message만 재정의하려면 response인자에 string을 전달하면 된다. 전체 JSON response body를 재정의하려면 response인자에 객체를 전달한다. Nest는 개체를 직렬화하고 JSON response body로 반환한다.

  • status 인자는 HTTP status code를 정의한다 .(위 코드에서 HttpStatus.FORBIDDEN)

    • 두 번째 생성자 인수  status는 유효한 HTTP 상태 코드여야 한다. 

      모범 사례는 @nestjs/common에서 가져온 HttpStatus enum을 사용하는 것이다.

  • 세 번째 생성자 인수 options (이름 그대로 선택사항)

    • 이 객체는 응답 객체로 직렬화되지 않지만 로깅 목적으로 유용할 수 있으며 이 객체를 발생시킨 내부 오류에 대한 정보를 제공한다 .

사용 예시 :

// cats.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
    });
  }
}

 위의 코드를 사용하면 다음가 같은 response가 반환된다.

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


Custom exceptions

  대부분의 경우 사용자 지정 예외를 작성할 필요가 없고 기본 제공 Nest HTTP 예외를 사용하면 된다. Custom exceptions를 만들어야 하는 경우HttpException가 기본 클래스에서 상속되는 자체 예외 계층 구조를 만드는 것이 좋습니다. 이 접근 방식을 사용하면 Nest가 예외를 인식하고 오류 응답을 자동으로 처리합니다.

// forbidden.exception.ts

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

 ForbiddenExceptionHttpException 를 상속 받으므로 기본 제공 예외 처리기와 원활하게 작동하므로 메서드 내에서 사용할 수 있습니다 findAll().

// cats.controller.ts

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


Built-in HTTP exceptions

  Nest는 base HttpException 에서 상속되는 일련의 표준 예외를 제공합니다. 이들은 @nestjs/common패키지에서 노출되며 가장 일반적인 많은 HTTP 예외를 나타냅니다.

  • BadRequestException

  • UnauthorizedException

  • NotFoundException

  • ForbiddenException

  • NotAcceptableException

  • RequestTimeoutException

  • ConflictException

  • GoneException

  • HttpVersionNotSupportedException

  • PayloadTooLargeException

  • UnsupportedMediaTypeException

  • UnprocessableEntityException

  • InternalServerErrorException

  • NotImplementedException

  • ImATeapotException

  • MethodNotAllowedException

  • BadGatewayException

  • ServiceUnavailableException

  • GatewayTimeoutException

  • PreconditionFailedException

 모든 기본 제공 예외들은 error cause 인자와options 인자(오류에 관한 설명)를 제공한다.

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

 위의 내용을 사용하면 응답이 다음과 같이 표시됩니다.

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


Exception filters

  기본(내장) 예외 필터가 자동으로 많은 경우를 처리할 수 있지만 예외 레이어에 대한 전체 제어가 필요할 수 있습니다. 예를 들어 로깅을 추가하거나 일부 동적 요인에 따라 다른 JSON 스키마를 사용할 수 있습니다.

  예외 필터는 바로 이러한 목적을 위해 설계되었습니다. 이를 통해 정확한 제어 흐름과 클라이언트로 다시 전송되는 응답 내용을 제어할 수 있습니다.

 HttpException클래스의 인스턴스인 예외를 포착하고 이에 대한 사용자 지정 응답 논리를 구현하는 예외 필터를 만들어 봅시다 . 이를 위해 기본 플랫폼 RequestResponse객체에 접근해야 합니다. Request객체 에 접근하여 원본 url을 추출 하고 로깅 정보에 포함할 수 있습니다. response.json()메서드를 사용하여 전송되는 Response object 를 직접 제어합니다.

//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,
      });
  }
}

 모든 exception filter들은 generic ExceptionFilter<T> interface를 구현해야한다. 이를 위해 catch(exception: T, host: ArgumentsHost) 메소드를 작성해야한다.

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

 @Catch()데코레이터는 단일 매개변수 또는 쉼표로 구분된 목록을 사용할 수 있습니다. 이를 통해 한 번에 여러 유형의 예외에 대한 필터를 설정할 수 있습니다.


Arguments host

  catch()메서드 의 매개변수를 살펴보자 . exception의 매개변수는 가장 최근에 처리된 exception 객체입니다.  ArgumentsHost실행 컨텍스트 장 에서 자세히 살펴볼 강력한 유틸리티 객체입니다 . 이 코드에서는 이를 사용하여 원래 request handler(예외가 발생한 컨트롤러)로 전달되는 객체 에 대한 참조를 얻습니다 . 이 코드에서는 ArgumentsHost의 도우미 메서드를 사용하여 원하는 Request ,Response객체를 가져 온다 .

Binding filters

  HttpExceptionFilter 메소드를 CatsController의 create() 메소드에 연결하는법.

// cats.controller.ts

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

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

  여기서 @UseFilters()데코레이터를 사용했습니다. @Catch()데코레이터 와 마찬가지로 단일 필터 인스턴스 또는 쉼표로 구분된 필터 인스턴스 목록을 사용할 수 있습니다. 여기에서 제자리에 HttpExceptionFilter의 인스턴스를 만들었습니다 . 또는 (인스턴스 대신) 클래스를 전달하고 인스턴스화에 대한 책임은 프레임워크에 맡기고 종속성 주입을 활성화할 수 있습니다 .

// cats.controller.ts

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

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

 가능한 경우 인스턴스 대신 클래스를 사용하여 필터를 적용하는 것이 좋습니다. Nest는 전체 모듈에서 동일한 클래스의 인스턴스를 쉽게 재사용할 수 있으므로 메모리 사용량이 줄어듭니다.

 위의 예에서  HttpExceptionFilter는 단일 create()route handler에만 적용되므로, 메서드 범위가 지정됩니다. 예외 필터는 메서드 범위, 컨트롤러 범위 또는 전역 범위와 같은 다양한 수준에서 범위를 지정할 수 있습니다. 예를 들어 필터를 컨트롤러 범위로 설정하려면 다음과 같습니다.

// cats.controller.ts

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

 이 구성은 CatsController에 정의된 모든 경로 핸들러에 대해 HttpExceptionFilter를 설정합니다.

// main.ts

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

  전역 범위 필터는 응용 프로그램 전체에서 모든 컨트롤러와 모든 라우트 핸들러에서 사용됩니다. 의존성 주입 측면에서 모듈 외부에서 등록된 전역 필터 는 모듈 컨텍스트 외부에서 수행되기 때문에 종속성을 주입할 수 없습니다. 이 문제를 해결하기 위해 아래 구문을 사용하여 모듈에서 직접 전역 범위 필터를 등록할 수 있습니다.


Catch everything

    모든 처리되지 않은 예외를 잡기 위해서는 예외 타입에 관계없이 @Catch() 데코레이터의 매개변수 리스트를 비워두면 됩니다.

 아래 예시에서는 HTTP adapter를 사용하여 응답을 전달하므로 플랫폼에 독립적인 코드입니다. 또한, 플랫폼에 특화된 객체(Request 및 Response)를 직접 사용하지 않습니다.

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);
  }
}


Inheritance

    일반적으로, 애플리케이션 요구 사항을 충족시키기 위해 만든 완전히 사용자 정의된 예외 필터를 만들게 됩니다. 그러나 특정한 요소에 기반해 동작을 덮어씌우려는 경우 내장된 기본적인 전역 예외 필터를 확장하고자 할 수 있습니다.

  기본 필터로 예외 처리를 위임하려면, BaseExceptionFilter를 확장하고 상속된 catch() 메서드를 호출해야 합니다.

// all-exceptions.filter.ts

import { Catch, ArgumentsHost } from '@nestjs/common';
import { BaseExceptionFilter } from '@nestjs/core';

@Catch()
export class AllExceptionsFilter extends BaseExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    super.catch(exception, host);
  }
}

  위 구현은 접근 방식을 보여주는 빈 껍데기일 뿐입니다. 확장된 예외 필터의 구현은 여러 조건을 처리하는 사용자 정의 비즈니스 로직을 포함해야합니다.

 전역 필터는 기본 필터를 확장할 수 있습니다. 이는 두 가지 방법 중 하나로 수행 할 수 있습니다.


첫 번째 방법은 사용자 정의 전역 필터를 인스턴스화 할 때 HttpAdapter 참조를 주입하는 것입니다.

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

  const { httpAdapter } = app.get(HttpAdapterHost);
  app.useGlobalFilters(new AllExceptionsFilter(httpAdapter));

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