[Nest.js] 전역으로 에러 핸들링 설정하기

Song·2021년 10월 23일
2

Node.js

목록 보기
7/7
post-custom-banner

전역으로 에러 핸들링을 처리하면 뭐가 좋을까?

에러 핸들링을 전역으로 사용하기 전에는 try-catch문을 이용하여 매 로직 코드마다 예외 처리를 해주었다. 이렇게 하나 하나씩 달아주다보니 나중에 수정 사항이 생길 때에도 일일이 변경해야하는 불편함이 있었는데 에러 핸들링을 전역으로 관리함으로 유지보수가 훨씬 편리해지고 코드를 매번 추가해주지 않아도 되어 더 깔끔하게 개발할 수 있다!

Nest.js의 내장 함수를 이용해보자

Nest.js 자체에는 예외 처리를 위한 내장 레이어(Built-in exception layers)가 포함되어있다. 로직 코드 내에서 예외 사항을 잡는 것이 아닌 특정 레이어에서 자동으로 잡아 response로 알려준다,

이러한 기능은 ExceptionFilter라는 내장 인터페이스를 통해 설정이 가능한데 기본적으로는 Http에서 발생하는 예외 사항 즉, HttpException 만 알아보고 나머지 에러는 internal-servel error로 때려버리기 때문에 오버라이딩을 통해 옵션을 세부화할 수 있다.

🔸 Step 1) ExceptionFilter 상속받기

Example 1) Nest.js 공식문서에서 제공하는 코드 (HttpException 전용)

// ./exception/http-exception.filter.ts 

import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Request, Response } from 'express';

@Catch(HttpException)	// ① @Catch 데코레이터를 이용하여 특정 예외사항을 정할 수 있다.
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({	// ② JSON 형태로 클라이언트에게 넘길 값을 설정할 수 있다.
        statusCode: status,
        timestamp: new Date().toISOString(),
        path: request.url,
      });
  }
}

@Catch() 데코레이터를 통해 처리하고 싶은 예외 사항을 정할 수 있다.

  • @Catch(HttpException): Http에서 관련한 예외 사항을 처리한다.
  • @Catch(에러1,에러2,에러3) 등 쉼표를 이용하여 여러 옵션을 추가할 수 있으며
  • @Catch() 같이 괄호안을 공란으로 두어 모든 예외 사항을 받을 수도 있다.

🔸 Example 2) 예외 처리를 세부화한 코드 (aka globalException)


import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { Request, Response } from 'express';
import { QueryFailedError } from "typeorm";

@Catch() // ()를 공란으로 두어 모든 예외처리를 받을 수 있도록 하였다.
export class GlobalExceptionFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();
    let status = HttpStatus.INTERNAL_SERVER_ERROR;
    let message = (exception as any).message.message;
    let code = 'HttpException';

    switch(exception.constructor) {
      case HttpException:	// for HttpException
        status = (exception as HttpException).getStatus();
        break;

      case QueryFailedError:  // for TypeOrm error
        status = HttpStatus.UNPROCESSABLE_ENTITY
        message = (exception as QueryFailedError).message;
        code = (exception as any).code;
        break;

      default: // default
          status = HttpStatus.INTERNAL_SERVER_ERROR
}
    response
      .status(status)
      .json({
        statusCode: status,
        message: (exception as any).message,
        timestamp: new Date().toISOString(),
        path: request.url,
      });
  }
}
  • @Catch() ()를 공란으로 두어 모든 예외처리를 받을 수 있도록 하였다.
  • switch() 문을 통해 옵션을 다르게 정의하였다.

🔸 Step 2) main.ts 와 app.module.ts 설정하기

./main.ts

import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { GlobalExceptionFilter } from './exception/GlobalExceptionFilter';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new GlobalExceptionFilter()); // useGlobalFilters() 를 선언하여 전역으로 범위를 설정
  await app.listen(3000);
}
bootstrap();

./app.module.ts

import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';
import { DayTimeTableModule } from './day-time-table/day-time-table.module';
import {GlobalExceptionFilter} from './exception/GlobalExceptionFilter';

@Module({
  imports: [DayTimeTableModule],
  controllers: [],
  providers: [{	// 의존성 주입이 가능하도록 module에도 설정해준다.
    provide: APP_FILTER,
    useClass: GlobalExceptionFilter,
  }],
})
export class AppModule {}

느낀점

정말 편리하다고 생각한다. Express에서도 전역으로 사용할 수 있는지 모르겠지만 최근에 진행한 팀 프로젝트에서도 이런 방법으로 예외 처리를 접근했으면 더 좋았을거라는 아쉬움이 남는다.
뭐..지금이라도 알아서 다행이다.

하지만 switch 문을 이용하여 모든 에러들을 일일이 명시해주고 그에 맞는 세팅을 해줘야하는 게 불편해보인다. 예를 들어 TypeORM도 하나 이상의 오류를 가지고 있는데 이렇게 라이브러리별로 발생하는 오류들을 한꺼번에 잡을 수 있다면..훨씬 편하지 않을까하는 생각도 든다. 공부하자!

참고 자료

profile
Learn From Yesterday, Live Today, Hope for Tomorrow
post-custom-banner

0개의 댓글