exception-filters

https://docs.nestjs.com/exception-filters

Nest 는 Exception Layer 를 가집니다.

Exception Layer#

앱으로 부터 처리되지 않은 모든 예외를 처리하여

  • 사용자 친화적인 응답으로 내보낸다.

global exception filter 전역 예외 필터#

HttpException 와 하위 클래스의 예외를 처리한다.

  • 이 외 예외는 exception filter 가 기본 JSON 응답을 생성한다.

    {
    "statusCode": 500,
    "message": "Internal server error"
    }
  • global exception filter 는 부분적으로 http-error 를 지원하는 라이브러리 이다.

    • statusCode 와 message 프로퍼티를 가지는 예외라면 적절한 응답을 반환한다.
    • (instead of the default InternalServerErrorException for unrecognized exceptions).

HttpException#

/**
* Defines the base Nest HTTP exception, which is handled by the default
* Exceptions Handler.
*
* @see [Base Exceptions](https://docs.nestjs.com/exception-filters#base-exceptions)
*
* @publicApi
*/
export declare class HttpException extends Error {
private readonly response;
private readonly status;
/**
* Instantiate a plain HTTP Exception.
*
* @example
* `throw new HttpException()`
*
* @usageNotes
* The constructor arguments define the response and the HTTP response status code.
* - The `response` argument (required) defines the JSON response body.
* - The `status` argument (required) defines the HTTP Status Code.
*
* By default, the JSON response body contains two properties:
* - `statusCode`: the Http Status Code.
* - `message`: a short description of the HTTP error by default; override this
* by supplying a string in the `response` parameter.
*
* To override the entire JSON response body, pass an object to the `createBody`
* method. Nest will serialize the object and return it as the JSON response body.
*
* The `status` argument is required, and should be a valid HTTP status code.
* Best practice is to use the `HttpStatus` enum imported from `nestjs/common`.
*
* @param response string or object describing the error condition.
* @param status HTTP response status code.
*/
constructor(response: string | Record<string, any>, status: number);
initMessage(): void;
initName(): void;
getResponse(): string | object;
getStatus(): number;
static createBody(objectOrError: object | string, description?: string, statusCode?: number): object;
}

표준 예외 던지기#

Nest 의 내장 HttpException 클래스

  • HTTP REST/GraphQL API 기반 어플리케이션에서, 특정 에러가 발생했을 때 HTTP 표준 응답을 주는 것이 Best Practice 이다.
cats.controller.ts: HttpException 생성자
@Get()
async findAll() {
throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}
for client response: JSON response body
{
"statusCode": 403,
"message": "Forbidden"
}

HttpException 생성자의 매개변수#

response

  • JSON Response Body message 를 정의
  • type: string | object

status

  • HTTP 상태 코드를 정의
export declare class HttpException extends Error {
private readonly response;
private readonly status;
constructor(response: string | Record<string, any>, status: number);
initMessage(): void;
initName(): void;
getResponse(): string | object;
getStatus(): number;
static createBody(objectOrError: object | string, description?: string, statusCode?: number): object;
}

JSON response body#

statusCode

  • status 인수 값의 HTTP status code

message

  • statusCode 에 대한 짧은 설명

JSON response body 재정의 하기#

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

사용자 정의 예외#

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

extends HttpException 로서 Nest 는 사용자 정의 예외임을 알 수 있다.

cats.controller.ts
@Get()
async findAll() {
throw new ForbiddenException();
}

대부분의 경우, 사용자 정의 예외보다 built-in Nest HTTP exception 을 사용 할 것임.

Built-in HTTP exceptions#

Nest 는 HttpException 을 상속한 표준 예외를 제공한다.

  • BadRequestException
  • UnauthorizedException
  • NotFoundException
  • ForbiddenException
  • NotAcceptableException
  • RequestTimeoutException
  • ConflictException
  • GoneException
  • HttpVersionNotSupportedException
  • PayloadTooLargeException
  • UnsupportedMediaTypeException
  • UnprocessableEntityException
  • InternalServerErrorException
  • NotImplementedException
  • ImATeapotException
  • MethodNotAllowedException
  • BadGatewayException
  • ServiceUnavailableException
  • GatewayTimeoutException
  • PreconditionFailedException

Exception filters#

기본(내장) 예외 필터가 많은 경우를 자동으로 처리할 수 있지만
예외 계층에 대한 Full Control 이 필요한 경우도 있다.

Exception filters 의 설계 목적#

정확한 제어 흐름과 클라이언트로 다시 전송되는 응답 내용을 제어한다.

  • logging 추가하기
  • dynamic factors 에 기반한 different JSON schema 사용
http-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Request, Response } from 'express';
// HttpException 클래스의 인스턴스를 catch 하는 클래스
@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();
// 커스텀 Reponse logic
response
.status(status)
.json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}
ExceptionFilter
import { ArgumentsHost } from '../features/arguments-host.interface';
export interface ExceptionFilter<T = any> {
/**
* Method to implement a custom exception filter.
*
* @param exception the class of the exception being handled
* @param host used to access an array of arguments for
* the in-flight request
*/
catch(exception: T, host: ArgumentsHost): any;
}

implements ExceptionFilter#

  • 모든 Exception filters 는 generic ExceptionFilter<T> 인터페이스를 구현해야 한다.
  • catch(exception: T, host: ArgumentsHost) 메서드를 제공한다.
  • T : 예외 타입

@Catch(HttpException)#

  • 필요한 metadata 를 Exception Filter 에게 바인드 한다.
  • HttpException 예외를 찾으라고 Nest 에게 이야기 해준다.
  • 하나 혹은 comma 로 구분된 리스트를 인자로 받는다.
import { Type } from '../../interfaces';
export declare function Catch(...exceptions: Type<any>[]): ClassDecorator;

Arguments host#

ExceptionFilter 클래스의 catch(exception: HttpException, host: ArgumentsHost) 메서드

  • exception

    • 현재 처리되고 있는 예외 객체
  • host

    • ArgumentsHost 객체
  • ArgumentsHost

    • 실행 컨텍스트[execution context] 를 알 수 있는 강력한 유틸리티 객체
    • http-exception.filter.ts 예시에서 알 수 있는 것들
      • 오리지널 Request 핸들러로 전달되는 Request 및 Response 객체
      • Request and Response 객체를 얻기 위한 헬퍼 메서드의 사용

바인딩 filters#

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

인스턴스 대신 클래스를 전달하여 프레임워크에 인스턴스화에 대한 책임을 맡기고 종속성 주입을 활성화할 수 있습니다.

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

필터 적용시, 인스턴스보다 클래스를 사용하는 것이 선호됩니다.

  • 클래스를 사용하는 방법은, 메모리 사용을 감소시킵니다.
  • Nest 는 모듈 간의 동일한 인스턴스를 쉽게 재활용 할 수 있기 때문임.
UseFilters
import { ExceptionFilter } from '../../index';
export declare const UseFilters: (...filters: (ExceptionFilter | Function)[]) => MethodDecorator & ClassDecorator;

예외 필터의 스코프 레벨#

  • method-scoped 메서드
  • controller-scoped 컨트롤러
  • global-scoped 전역
cats.controller.ts :: 컨트롤러 스코프의 예외 필터 → CatsController 의 모든 라우터 핸들러에 적용
@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();
warning

useGlobalFilters() 메서드는 gateways 혹은 hybrid applications 에 대해 필터를 설정하지 못한다.

  • 전역 스코프 필터는, 어플리케이션의 모든 곳(컨트롤러, 라우트 핸들러) 에서 사용될 수 있다.

  • 의존성 주입의 관점에서, 전역 스코프 필터는 모듈 외부에서 등록 되었기 때문에 종속성 주입을 할 수 없다.

    • 모듈 컨텍스트 외부에서 수행되기 때문이다.
  • 해결 방법으로, 전역 스코프 필터를 모듈에 바로 등록하여 종속성 주입을 사용할 수 있다.

    app.module.ts
    import { Module } from '@nestjs/common';
    import { APP_FILTER } from '@nestjs/core';
    @Module({
    providers: [
    {
    provide: APP_FILTER,
    useClass: HttpExceptionFilter,
    },
    ],
    })
    export class AppModule {}

Catch everything @Catch()#

처리되지 않은 모든 예외를 캐치하려면 @Catch() (empty parameter) 를 사용한다.

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

platform-agnostic

  • response 를 전달하기 위해 HTTP adapter 를 사용한다.
  • platform-specific objects (Request and Response) 를 직접적으로 사용하지 않음.

Inheritance#

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);
// more business logic ...
}
}
  • 일반적으로, 어플리케이션의 요구사항을 이행하기 위해 fully customized exception filters 를 생성한다.
  • 그러나, 간단히 빌트인 default global exception filter 를 사용하고 싶은 케이스도 있다.
    • BaseExceptionFilter 를 확장하고 확장된 catch() 메서드를 호출한다.
    • BaseExceptionFilter 를 확장한 Method-scoped 와 Controller-scoped 필터는 new 로 초기화 되면 안된다.
    • 프레임워크가 자동으로 초기화 한다.

전역필터로 등록하기#

1. useGlobalFilters 사용
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const { httpAdapter } = app.get(HttpAdapterHost);
app.useGlobalFilters(new AllExceptionsFilter(httpAdapter));
await app.listen(3000);
}
bootstrap();
  1. 모듈에 APP_FILTER 사용하기
Last updated on