이번 포스팅에선 NestJS 에서 제공하는 예외 필터 기능을 사용해보겠습니다.
- 예외 (Exception)
예외 처리는 무언가를 개발하는 과정에선 필수 사항인 것 같습니다. 개발자는 항상 에러가 발생할 수 있는 상황을 대비해야하고, 그에 따라
빠르게 에러를 전파하던지 또는 아예 무시되도록 하던지 하는 방법을 선택할 수도 있죠.
예전에 Express 로 처음 개발할 땐 에러 처리를 controller 부분에서 필수 파라미터가 undefined 일 땐 res.json(400)... 를,
그 이후 로직에는 전체에 try - catch 를 걸어서 뭔 에러던 다 catch 에서 잡고 500 으로 처리했었습니다.
이러다보니 코드 전체에 try - catch 및 res.json.. 이 난무했고, 보기엔 상당히 지저분했죠. 이런 것도 AOP 측면에서 코드를 좀 더 깔끔히
만들 수 있을텐데, 그런 내용도 이번에 약간이나마 넣어보도록 하겠습니다.
- 예외 처리
NestJS 는 프레임워크 내에 예외 레이어를 별도로 두고 있습니다. 이 레이어에선 어플리케이션에서 처리하지 못한 예외를 처리하는 역할을
합니다. 별다른 처리를 하지 않았을 때 이 레이어에서 예외를 어떻게 처리하는지 먼저 보도록 하겠습니다.
...
@Controller('ads')
export class AdsController {
...
@Get(':id')
fidOne(@Param('id') id: number) {
return this.adsService.findOne(id);
}
}
기존 코드의 AdsController 에서 /ads/:id 요청을 처리하던 부분에 ParseIntPipe 만 제거했습니다.
id 에 의도적으로 string 을 넣어서 이 에러가 어떻게 처리되는지를 보기 위함입니다.
원래는 ParseIntPipe 에서 파라미터를 validation 할 때 실패로 떨어질테지만요.
number 가 전달되어야 할 파라미터에 string 을 전달했을 때 NestJS 가 처리하는 내용입니다.
예외 레이어가 별도로 이 요청을 500 에러로 처리하고 앱은 여전히 요청을 처리할 수 있는 상태입니다.
위처럼 에러에 대한 응답을 JSON 형식으로 바꿔주는 것 또한 기본 내장된 예외 레이어가 처리합니다.
import {
...,
BadRequestException,
} from '@nestjs/common';
...
@Controller('ads')
export class AdsController {
...
@Get(':id')
fidOne(@Param('id') id: number) {
if (id < 1) {
throw new BadRequestException('id 는 0 보다 큰 정수여야 합니다.');
}
return this.adsService.findOne(id);
}
}
위 코드는 예외에 대해 NestJS 가 제공하는 예외 클래스를 사용한 예시입니다.
실제로 이렇게 하지는 않을테지만, NestJS 가 제공하는 예외 클래스를 어떻게 활용하면 되는지를 간단하게 예시로 가져와 봤습니다.
NestJS 가 제공하는 모든 예외는 HttpException 을 상속합니다. 그리고 예외 레이어에선 HttpException 도 아니면서
HttpException 을 상속하지도 않는 예외에 대해선 전부 InternalServerErrorException 으로 처리합니다.
- 예외 필터
NestJS 에선 기본으로 내장되어 있는 예외 레이어 외에 직접 예외 필터 레이어를 작성해 처리할 수 있습니다.
일반적으론 예외가 발생했을 때 로그를 남기거나 응답 객체를 원하는대로 커스터마이징하고 싶을 때 사용합니다.
저는 이번 예시로 TypeORM 에서 발생하는 에러를 잡는 예외 필터를 한번 작성해보겠습니다.
import {
ArgumentsHost,
Catch,
ExceptionFilter,
HttpStatus,
} from '@nestjs/common';
import { Response } from 'express';
import { QueryFailedError, TypeORMError } from 'typeorm';
@Catch(TypeORMError)
export class TypeORMExceptionFilter implements ExceptionFilter {
catch(exception: Error, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const res = ctx.getResponse<Response>();
let status = HttpStatus.INTERNAL_SERVER_ERROR;
switch (exception.constructor) {
case QueryFailedError:
status = HttpStatus.BAD_REQUEST;
break;
default:
break;
}
const log = {
timestamp: new Date(),
message: exception.message,
};
res.status(status).json(log);
}
}
방식은 NestJS 에서 제공하는 ExceptionFilter 를 구현하는 TypeORMExceptionFilter 클래스를 먼저 만들어줍니다. catch 메서드를 원하는대로 구현하면 됩니다. 해당 필터 위에 Catch 데코레이터는 처리되지 않는 예외를 잡을 때 사용하는데, 아무런 인자도 넣지 않으면
모든 예외를 잡게 됩니다. 저는 TypeORM 과 관련된 에러만 잡기 위해 TypeORMError 를 인자로 넣었습니다.
실제로 TypeORM 의 에러라면 응답 코드는 500 이 적절할테고 production 환경에선 특별히 메세지를 보여줄 필요도 없지만 예시로
보이기 위해 400 에러로 처리하고 에러 메세지를 응답에 포함하도록 했습니다.
import {
...,
UseFilters,
} from '@nestjs/common';
import { AdsService } from './ads.service';
import { CreateAdDto } from './dto/create-ad.dto';
import { TypeORMExceptionFilter } from './aop/exception-filter';
@Controller('ads')
export class AdsController {
...
@UseFilters(TypeORMExceptionFilter)
@Get(':id')
findOne(@Param('id') id: number) {
return this.adsService.findOne(id);
}
}
위에서 깜빡했는데 새로 작성한 TypeORMExceptionFilter 는 src 하위에 aop 라는 디렉토리를 만들고 그 하위에 작성했습니다.
작성한 예외 필터는 위와 같이 UseFilters 데코레이터를 사용해 특정 엔드포인트에 적용할 수 있습니다.
이제 예외 필터를 적용한 엔드포인트에 이전과 동일한 요청을 보내보면, 의도한대로 400 에러로 처리되면서 응답에 에러 메세지도
포함되는 것을 확인할 수 있습니다. 예시가 좀 간단하긴 합니다만 예외 필터에서 이보다 더 무언가를 할 필요도 없긴 합니다.
NestJS 가 제공하는 예외 필터를 활용해 좀 더 관점 지향 프로그래밍을 할 수 있게 되었고, 예시에서 적용한 특정 엔드포인트 외에도
특정 컨트롤러 또는 어플리케이션 전체에 적용도 가능하니 관리하는 어플리케이션에 필요한 경우 참고해보시면 좋을 듯 합니다.
'Backend > NestJS' 카테고리의 다른 글
NestJS - 대충 서비스 만들어보기 (15) (0) | 2023.07.01 |
---|---|
NestJS - 대충 서비스 만들어보기 (14) (0) | 2023.06.17 |
NestJS - 대충 서비스 만들어보기 (12) (0) | 2023.05.27 |
NestJS - 대충 서비스 만들어보기 (11) (0) | 2023.05.20 |
NestJS - 대충 서비스 만들어보기 (10) (0) | 2023.05.13 |