ExceptionFilter를 활용한 에러 핸들링

Dongwon Ahn·2024년 2월 25일
0

nestjs

목록 보기
1/1
post-thumbnail

이번에 사이드 프로젝트로만 경험해본 nestjs를 신규 프로젝트에 사용 중에 있습니다.
프로젝트 중간중간 정리 및 공유하기 좋겠다 싶은 내용들을 정리하는 시리즈 입니다.

Nestjs의 기본 예외 처리

nestjs는 기본적으로 응용 프로그램 전반에 걸쳐 처리되지 않는 모든 예외를 처리하는 exceptions layer가 내장되어 있습니다. 코드에서 따로 예외를 처리하지 않았을 때 이 layer가 에러를 핸들링하여 HttpException 타입의 예외를 처리해줍니다.

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

또한 @nestjs/common package를 통해 아래 HttpException를 상속받은 여러 표준 HTTP 응답 에러 class를 제공합니다.
예를 들어 클라이언트에 400 에러를 전달해야되면 BadRequestException 를 아래와 같이 사용하면 됩니다.

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

그럼 클라이언트는 아래와 같은 400 에러 메세지를 전달받을 수 있습니다.

{
  "statusCode": 400,
  "message": "Bad Request"
}

Exception filters란

Nestjs에서 제공해주는 예외 처리 방식을 그대로 사용할 수 있지만, 로깅 또는 sentry 또는 slack 알림 등 처리를 위해 Exception filters를 통해 커스티마이징을 진행할 수 있습니다.

다음은 공식문서에서 제공해주는 exeception filters 예제입니다.

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

우선 @Catch(HttpException) 데코레이터는 해당 filter가 HttpException에 속하는 에러가 있는지 체크를 진행합니다.(해당 체크에 통과된 에러를 filter 진행합니다.)
@Catch는 파라미터가 하나일 수도 있고 콤마로 여러 개를 구분할 수 있습니다.

// @nestjs/common에 구현된 @Catch 
export declare function Catch(
  ...exceptions: Array<Type<any> | Abstract<any>>
): ClassDecorator;

HttpExceptionFilter class안의 catch의 파라미터를 살펴보겠습니다.
exception는 현재 처리되고 있는 에러의 object 입니다. host는 ArgumentsHost는 request handler를 통해 통과된 responserequest를 가져오기 위해 사용되었습니다.

// ArgumentsHost의 switchToHttp 함수가 반환하는 타입
export interface HttpArgumentsHost {
    /**
     * Returns the in-flight `request` object.
     */
    getRequest<T = any>(): T;
    /**
     * Returns the in-flight `response` object.
     */
    getResponse<T = any>(): T;
    getNext<T = any>(): T;
}

catch에 프로젝트에 따라 로깅이나, slack 알림 명령어 등을 추가할 수 있습니다.

Catch everything

위 예제로 진행하는 경우 @Catch를 통해 체크되는 예외만 전달되게 됩니다.
모든 에러를 핸들링하고 싶은 경우 아래와 같이 변경을 진행하면 됩니다.

// Catch에 아무런 값도 넣지 않는다.
@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
  // exception을 unknown 으로 변경
  catch(exception: unknown, host: ArgumentsHost) {
    const httpStatus =
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;
    // 생략...

Binding Filters

위에서 새로 만든 Filter를 적용하는 방법은 아래와 같습니다.

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

우선 @UseFilters를 통해 특정 api만 적용을 할 수 있습니다.
또한 controller에 적용을 할 수 있습니다.

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

애플리케이션 전역에서 작동을 할 수 있도록 global-scoped filter 를 설정할 수 있습니다. main.ts파일 안에 아래 예제와 같이 useGlobalFilters를 통해 설정할 수 있습니다.

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

Global-scoped filters는 모든 컨트롤러 및 모든 경로 처리기에 대해 전체 응용 프로그램에 걸쳐 사용됩니다. 종속성 주입 측면에서 임의의 모듈 외부에서 등록된 전역 필터는 종속성을 주입할 수 없습니다. 그렇기에 아래와 같이 직접 Global-scoped filters를 등록을 필요로 합니다.

//app.module.ts
import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_FILTER,
      useClass: HttpExceptionFilter,
    },
  ],
})
export class AppModule {}
profile
Typescript를 통해 풀스택 개발을 진행하고 있습니다.

0개의 댓글