nestjs_filter로 에러 처리하기
filter로 에러 처리하기
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,
});
}
}
}