Research/Nest.js

nestjs_filter로 에러 처리하기

RIEM 2023. 4. 4. 16:39
728x90

filter로 에러 처리하기

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

filter는 라우터와 파이프 이전 단계에서 에러를 핸들링할 수 있는 유용한 기능이다.

에러를 처리하는 전략으로 일반 필터링과 글로벌 필터링이 있다. 일단 어떤 에러라도 처리하고 싶다면 글로벌 필터를 main 파일에 적용해버리면 마음 편하고, 좀 더 세부적인 영역을 타겟팅하고 싶다면 클래스나 핸들러 단위로 @UseFilter 데코레이터를 적용하여 사용하면 된다.

에러 발생

에러 발생 시 nest.js의 디폴트 반환 객체에는 statudCode, message, error 메시지 이 3가지 정보가 담겨있다.

@Controller('cats')
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

  @Get()
  getAllCat() {
    // Node.js : throw new Error
    throw new HttpException('api is not available', 401);
    return 'all cat';
  }
  
  ...

컨트롤러의 api에서 에러를 의도적으로 발생시켰다. node.js와 달리 nest.js에는 HttpException으로 구체적으로 에러를 던져줄 수 있다.

 

커스텀 오버라이딩 에러 메시지 

@Controller('cats')
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

  @Get()
  getAllCat() {
    // Node.js : throw new Error
    throw new HttpException(
      {
        success: false,
        myMessage: 'api is not available 😭',
      },
      401,
    );
    return 'all cat';
  }
  ...

 

메시지를 오버라이딩하여 나만의 메시지 구조를 만든다. 단, 재사용성이 부족하다는 문제가 있다. 이를 위해 필터를 따로 만들어주자.

커스텀 필터 생성

nest.js 공식문서에서 제공하는 기본 필터다.
https://docs.nestjs.com/exception-filters

// src/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();
    const error = exception.getResponse(); // 개발자가 작성한 에러 메시지

    // express.js : res.status(404).send({'msg': 'It was not found'});
    response.status(status).json({
      success: false,
      timestamp: new Date().toISOString(),
      path: request.url,
      statusCode: status,
      error: error,
    });
  }
}

 

핸들러에 커스텀 필터 적용

...

  @Get()
  @UseFilters(HttpExceptionFilter)
  getAllCat() {
    // Node.js : throw new Error
    throw new HttpException('api is not available 😭', 401);
    return 'all cat';
  }
  
...

에러가 발생할 것 같은 핸들러에 @UseFilter 데코레이터로 해당 필터를 적용해주면 된다.

위 필터에서 정의했던 statusCode, timpestamp, path가 찍혀나오는 이유는 위 요청이 우리가 만든 커스텀 필터를 거쳤기 때문이다. 

 

클래스에 커스텀 필터 일괄 적용

핸들러 하나 뿐만 아니라 핸들러들을 아우르는 클래스에 필터를 입힐 수도 있다.

...
@Controller('cats')
@UseFilters(HttpExceptionFilter)
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

  @Get()
  getAllCat() {
    // Node.js : throw new Error
    throw new HttpException('api is not available 😭', 401);
    return 'all cat';
  }

  @Get(':id')
  getOneCat() {
    return 'one cat';
  }
...

@Controller 데코레이터 바로 아래 @UseFilters를 먹인 것을 볼 수 있다.

글로벌 필터 적용

// main.ts

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

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

클래스만으로도 잡지못하는 에러가 있다. 왜냐하면 클래스 밖의 세상은 넓기 때문이다. 예상하지 못하는 영역은 그냥 글로벌 필터로 처리해버리면 쉽게 대응할 수 있다.

 

글로벌로 처리하면 에러 메시지에 객체가 대입되어 가독성이 떨어지고 데이터의 중복이 생긴다. 따라서 글로벌이 아닐 때 문자열을 받는 error와 글로벌일 때 객체를 받는 error, 두 조건을 고려하여 분기처리 해주자.

// src/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();
    const error = exception.getResponse() as
      | string
      | { error: string; statusCode: number; message: string | string[] };

    // 핸들러 에러 처리 시 error메시지가 단순 스트링일 경우
    if (typeof error === 'string') {
      response.status(status).json({
        success: false,
        timestamp: new Date().toISOString(),
        path: request.url,
        error: error,
      });
    } else {
      // 글로벌에서 에러 처리 시 error메시지가 객체일 경우
      response.status(status).json({
        success: false,
        timestamp: new Date().toISOString(),
        ...error,
      });
    }
  }
}

 

728x90