[NestJS 공식문서 정독하기] Overview - Exception filters

2022. 7. 31. 22:21Web/NestJS

반응형
🔗  https://docs.nestjs.com/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 Progamming), FP (Functional Programming), and FRP (Functional Reac

docs.nestjs.com

  • NestJS는 처리되지 않은 exception을 처리하는 exception layer 를 내장하고 있음
  • 해당 레이어는 애플리케이션 코드에서 처리되지 않은 exception을 catch해서 정제된 응답을 전송함
  • 기본적으로 내장된 global exception filter가 HttpException 타입과 그 하위 타입의 예외들을 처리함
  • 알 수 없는 예외의 경우(HttpException 이 아님)에는 500 Internal server error 를 디폴트로 응답함
  • 기본 exception filter는 statusCode와 message가 존재하는 exception이면 정상적으로 catch하고 응답함 (http-errors 라이브러리를 일부 지원함)

Standard exceptions

  • NestJS는 기본적으로 HttpException 이라는 클래스를 제공함
  • 전형적인 REST/GraphQL API 기반 애플리케이션에서는 해당 클래스를 사용하는 것을 추천함
  • HttpException 의 생성자는 response와 status 두 가지 파라미터를 받음
  • response에 string을 넣으면 응답의 message로 전달되고, object를 넣으면 response 전체가 해당 object가 됨
  • status는 유효한 HTTP 상태 코드여야함 (HTTP 상태 코드 enum인 HttpStatus 를 활용하는 것이 best practice임)

Custom exceptions

  • 대부분의 경우에는 만들 필요 없음 (내장 exception 목록)
  • 다만 만들어야 할 경우에는 자체 위계 구조를 만들고, 모두 최상위 부모로 HttpException 을 상속 받는 구조를 추천함
  • 그러면 기본 exception filter가 custom exception을 인식하고 처리할 수 있음

Exception filters

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 layer의 동작 방식을 모두 직접 조작하고 싶으면 exception filter를 직접 구현할 수 있음
  • 모든 exception filter는 ExceptionFilter<T> 인터페이스를 구현해야 함 (T는 예외의 타입)
  • @Catch() 는 해당 exception filter에 metadata를 바인딩 해줌 (파라미터로 하나 이상의 예외 타입을 받을 수 있음)

Arguments host

  • 위에서 구현하는 catch() 메소드의 파라미터 중 host는 ArgumentsHost 객체임
  • ArugmentsHost 는 강력한 유틸리티 객체로 execution context 챕터에서 더 자세하게 다룸
  • 위 코드에서는 본래의 요청 핸들러 함수에 전달된 RequestResponse 객체를 가져오기 위해 사용함

Binding filters

@Post()
@UseFilters(new HttpExceptionFilter())
async create(@Body() createCatDto: CreateCatDto) {
  throw new ForbiddenException();
}
@Post()
@UseFilters(HttpExceptionFilter)
async create(@Body() createCatDto: CreateCatDto) {
  throw new ForbiddenException();
}
@UseFilters(new HttpExceptionFilter())
export class CatsController {}
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new HttpExceptionFilter());
  await app.listen(3000);
}
bootstrap();
  • 위와 같이 Controller의 메소드, Controller 클래스, 혹은 글로벌하게 exception filter의 scope를 적용할 수 있음
  • @UseFilter() 데코레이터에는 인스턴스 혹은 클래스 자체를 파라미터로 전달할 수 있는데, 클래스를 전달하면 NestJS의 DI를 통해 인스턴스를 재사용할 수 있게 되기 때문에 클래스를 전달하는 것을 추천함 (메모리 사용량 감소)
import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_FILTER,
      useClass: HttpExceptionFilter,
    },
  ],
})
export class AppModule {}
  • useGlobalFilters() 를 사용해서 글로벌 scope의 exception filter를 설정하면 DI를 활용할 수 없음
  • 따라서, 위와 같이 모듈에 직접 등록하여 DI를 활용할 수도 있음
  • 다만, 어떤 모듈에 등록을 하더라도 exception filter는 글로벌 scope로 등록됨 (모듈과 무관)

Catch everything

  • 처리되지 않은 모든 예외를 catch하기 위해서는 @Catch() 파라미터로 아무것도 전달하지 않으면 됨

Inheritance

@Catch()
export class AllExceptionsFilter extends BaseExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    super.catch(exception, host);
  }
}
  • 내장된 global exception filter를 extend해서 사용하고 싶은 경우에는 BaseExceptionFilter 를 extend하여 사용할 수 있음
반응형