백앤드(Back-End)/Nest.js

[Nest.js] Nest.js API 만들기 (10) - 예외처리(Exception Filters)

RyanSin 2021. 11. 27. 14:19
반응형

- 개요

안녕하세요. 이번 시간에는 Nest.js에서 Filter를 사용해서 에러를 처리하는 방법에 대해 알아보겠습니다.

 

- 개념

Nest.js에서 여러 로직을 처리하다 보면 예외처리를 해야 하는 상황이 있습니다. 

 

간혹 DB에서 특정 유저를 조회했을 때 해당 유저가 없다면, 클라리언트에게 유저가 존재하지 않다는 정보를 알려줘야 합니다.

 

또는 SQL구문에 문제가 있어 처리해야 하는 상황에서도 우리는 예외처리를 해야 합니다.

 

우리는 서버에서 수많은 로직들에 예외처리를 해야 하고 해당 예외 정보를 수집해, 유지보수해야 합니다.

 

Nest.js에서는 아래와 같이 Filter를 설명하고 있습니다.

 

출처 : https://docs.nestjs.kr/exception-filters

개발자가 특별한 예외를 처리하지 않는다면 Nest.js에서는 내장된 예외 레이어가 이를 처리한다고 나와있습니다.

 

그렇다면 해당 Filter를 사용해서 우리가 원하는 방법으로 Filter를 만들어 사용해 보겠습니다.

 

- ExceptionFilter

FIlter 적용은 어렵지 않습니다. ExceptionFilter를 상속받는 클래스를 생성하고 해당 예외 처리 내용을 클라이언트에게 알맞게 전송하면 됩니다. 아래 코드를 확인해 보겠습니다.

 

import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
  HttpStatus,
} from '@nestjs/common';

/**
 * @Catch(HttpException)
 * 해당 데코레이터는 필요한 메타 데이터를 ExceptionFilter에 바인딩하여,
 * 필터가 HttpException 타입의 예외만 찾고 있다는 것을 Nset.js에 알리기 위해 선언한다.
 */
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  /**
   * @author Ryan
   * @description 예외 처리 함수
   *
   * @param exception 현재 처리 중인 예외 객체
   * @param host ArgumentsHost 객체 -> 핸들러에 전달되는 인수를 검색하는 메서드를 제공한다 (Express를 사용하는 경우 - Response & Request & Next 제공)
   */
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();
    
    const status =
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;

    /**
     * @author Ryan
     * @description HttpException에서 전송한 데이터를 추출할 때 사용
     */
    const res: any = exception.getResponse();

    /* 클라이언트에게 정보를 전달한다. */
    response.status(status).json({
      success: false,
      message: res.message,
    });
  }
}

위 코드에서 중요한 부분은 catch( cexecption: HttpException, host: ArgumentsHost) 메서드입니다.

두 파라미터 값을 통해 우리는 원하는 예외 상태를 수집하고 클라이언트에게 알맞은 정보를 제공할 수 있습니다.

 

생성한 필터는 우리가 원하는 곳에 연결해서 사용할 수 있습니다.

 

1. Controller 연결

  @Get() //경로를 설정하지 않으면 "user/" 경로로 설정이 된다.
  @UseFilters(new HttpExceptionFilter())
  getHelloWorld(): string {
    return this.userService.getHelloWorld();
  }

 

Controller 부분에서 UseFilters 데코레이터를 사용해서 연결할 수 있습니다.

 

 

2. 전역 연결

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
import { HttpExceptionFilter } from './utils/http-exception.filter';

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

  //예외 필터 연결
  app.useGlobalFilters(new HttpExceptionFilter());

  //Global Middleware 설정 -> Cors 속성 활성화
  app.enableCors({
    origin: '*',
    methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
    optionsSuccessStatus: 200,
  });

  app.useGlobalPipes(
    new ValidationPipe({
      /**
       * whitelist: DTO에 없은 속성은 무조건 거른다.
       * forbidNonWhitelisted: 전달하는 요청 값 중에 정의 되지 않은 값이 있으면 Error를 발생합니다.
       * transform: 네트워크를 통해 들어오는 데이터는 일반 JavaScript 객체입니다.
       *            객체를 자동으로 DTO로 변환을 원하면 transform 값을 true로 설정한다.
       * disableErrorMessages: Error가 발생 했을 때 Error Message를 표시 여부 설정(true: 표시하지 않음, false: 표시함)
       *                       배포 환경에서는 true로 설정하는 걸 추천합니다.
       */
      whitelist: true,
      forbidNonWhitelisted: true,
      transform: true,
      disableErrorMessages: true,
    }),
  );
  await app.listen(3000);
}
bootstrap();

 

위 코드에서 app.useGlobalFilters(new HttpExceptionFilter()); 코드를 선언해서 서버 전체에서 예외를 처리할 수 있게 합니다

 

그럼 몇 가지 테스트를 통해 실제 사용하는 방법을 눈으로 확인해 보겠습니다.

 

지난 시간에 TypeORM으로 생성한 유저 테이블에 user_id 값은 @Unique 데코레이터를 통해 고유하게 설정했습니다.

 

그럼 중복되는 유저를 생성한다면 DB에서는 API 서버에게 에러를 발생합니다.

Ex) HttpException

예외를 처리하는 방법으로 우리는 HttpExection 클래스를 새롭게 생성해 해당 정보를 설정해서 전달하는 방식을 사용합니다.

 

UserRepository 클래스에서 onCreate 메서드에서 유저를 생성하는 상황에서 동일한 유저 이름을 생성하거나 잘 못 된 값을 설정한다면 어떻게 될까요? 결과는 아래와 같이 처리할 수 있습니다.

 

@EntityRepository(User)
export class UserRepository extends Repository<User> {
  //유저 생성
  async onCreate(createUserDto: CreateUserDto): Promise<boolean> {
    try {
      const { user_id, password, name, age } = createUserDto;

      const user = await this.save({
        user_id,
        password,
        salt: '임시',
        name,
        age,
      });

      return user ? true : false;
    } catch (error) {
      throw new HttpException(
        {
          message: 'SQL에러',
          error: error.sqlMessage,
        },
        HttpStatus.FORBIDDEN,
      );
    }
  }
}

디테일하게 정보를 전달하고 싶다면 swict/case 구문을 사용해서 디테일하게 만들 수 있지만 지금은 DB에서 중복되는 유저를 등록하는 에러를 발생하는 부분을 보여드리겠습니다.

 

ryan 유저 생성

 

ryan 유저가 없다면 성공 값인 true를 반환했습니다. 하지만 같은 유저를 등록하게 되면 서버는 아래와 같이 에러를 반환합니다.

 

같은 ryan 유저 생성

 

우리가 설정한 message 값을 반환하는 걸 확인할 수 있습니다. 하지만 어떤 에러인지는 알 수 가 없죠... 그 부분은 HttpExceiptionFilter 클래스에 확인 할 수 있습니다. 아래와 같이 전에 Error 정보를 담아 전송했기 때문에 이 부분을 캐치해 에러 로그를 수집할 수 있습니다.

 

import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
  HttpStatus,
} from '@nestjs/common';

/**
 * @Catch(HttpException)
 * 해당 데코레이터는 필요한 메타 데이터를 ExceptionFilter에 바인딩하여,
 * 필터가 HttpException 타입의 예외만 찾고 있다는 것을 Nset.js에 알리기 위해 선언한다.
 */
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  /**
   * @author Ryan
   * @description 예외 처리 함수
   *
   * @param exception 현재 처리 중인 예외 객체
   * @param host ArgumentsHost 객체 -> 핸들러에 전달되는 인수를 검색하는 메서드를 제공한다 (Express를 사용하는 경우 - Response & Request & Next 제공)
   */
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();

    const status =
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;

    /**
     * @author Ryan
     * @description HttpException에서 전송한 데이터를 추출할 때 사용
     */
    const res: any = exception.getResponse();

    //요청 url 및 에러 정보
    const url: string = request.url;
    const error: string = res.error;
    const timestamp: string = new Date().toISOString();

    console.log('요청 url : ', url);
    console.log('error 정보 : ', error);
    console.log('발생 시간 : ', timestamp);

    /* 클라이언트에게 정보를 전달한다. */
    response.status(status).json({
      success: false,
      message: res.message,
    });
  }
}

 

실행 결과

error 정보

요청 url 정보 값과 Error 발생 시간 그리고 우리가 원하는 Error 내용을 확인 할 수 있습니다.

 

해당 정보를 로그에 기록하고 나중에 분석하는 데 사용할 수 있습니다.

또한 클라이언트에게 실제 error 메시지가 아닌 프런트 앤드와 백앤드에서 협의한 응답 값을 통해 클라이언트에게 알맞은 에러 메시지를 제공합니다.

 

이번 시간에는 Nest.js에서 Filter에 대해 알아봤습니다. 실습을 통해 꼭 직접 확인하시는 걸 추천드리겠습니다. 감사합니다.

소스 저장소

github: https://github.com/Ryan-Sin/Node_Nest/tree/v9

 

GitHub - Ryan-Sin/Node_Nest

Contribute to Ryan-Sin/Node_Nest development by creating an account on GitHub.

github.com