class-validator와 @UsePipe데코레이터를 이용한 유효성 검사나, Passport-jwt와 @UseGuard를 이용한 토큰 검사를 실시해서 에러가 발생되었을 때 나오는 메시지나 스테이터스 코드는 각 라이브러리가 가진 내장된 original 에러메시지가 발생한다.
물론 메시지만 보고 에러를 인터셉트해서 처리할 프론트 개발자가 못 알아볼 일은 없겠지만, 우리가 미리 명세서를 정해서 특정메시지를 주고받기로 약속한 상태라면 이러한 메지시는 띄우면 안될 뿐더러, 보안측면상 에러의 메시지는 오리지날 메시지를 띄워주는 것은 별로 좋지 않다는 포스팅을 봤었다.
NestJS에서는 에러예외를 처리하기 위한 미들웨어가 따로 존재한다. 저번 포스팅의 Pipe는 유효성검사를, Guard는 인증검사를 하는 미들웨어인 것처럼, 에러예외검사를 위해 @UseFilters라는 데코레이터를 사용한 예외처리를 진행할 수 있다.
@Post()
@UseFilters(new HttpExceptionFilter())//<< @UseFilters데코레이터 사용
async create(@Body() createCatDto: CreateCatDto) {
throw new ForbiddenException();
}
HttpException클래스의 인스턴스인 예외를 포착하고 에러에 대해서 내가 원하는 응답 로직을 구현하는 예외 필터 HttpExceptionFilter()를 만들어보자.
NestJS공식페이지에서 제공하는 예외필터의 예시는 아래와같다.
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();
response
.status(status)
.json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}
예외가 발생하였을 때 스테이터스코드와, 시간, 어떤경로로 왔는지만 예외를 보내준다. 따로 메시지를 연결해 주지 않는다. 내가 원하는 방식은 각 에러에 대해 내가원하는 메시지를 보내주기 위한 방식의 filter이다.
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
} from '@nestjs/common';
import { Response } from 'express';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
console.error(exception);
const ctx = host.switchToHttp();
const res = ctx.getResponse<Response>();
const status = exception.getStatus();
const err = exception.getResponse() as
| { message: any; statusCode: number }
// class-validator에서 발생한 에러 식별
| { error: string; statusCode: 400; message: string[] };
if (typeof err !== 'string') {
// class-validator에서 발생한 에러 처리
if (err.statusCode === 400) {
const [errorDetails] = err.message;
return res.status(status).json({
errorMsg: '요청한 데이터 형식을 확인해주세요.',
errorDetails,
});
}
// JWT 인증 실패 에러 처리
if (err.statusCode === 401) {
return res.status(status).json({
errorMsg: '로그인 후 사용 가능합니다.',
});
}
// 500에러 처리
if (err.statusCode === 500) {
return res.status(status).json({
errorMsg: '서버 오류가 발생했습니다.',
});
}
}
// 일부 코드에서 직접 throw로 new Httpexception을 보내줄 때 처리
return res.status(status).json({ errorMsg: exception.message });
}
}
Response에서 err에 에러 객체를 담고 각 err을 분기처리하여 필요한 양식으로 return해주었다. 위와같이 처리하고 원하는 곳에 @UseFliters데코레이터를 사용해 적용시키면 된다.
필자가 작성한 에러필터는 모든 종류의 에러를 분기처리를 하는 방식으로 진행하였기 때문에 라우터레벨이나, 클래스레벨이 아닌 전역으로 설정하여 처리하기 위해 @UseFilters데코레이터가 아니라 main.ts에 app.useGlobalFilters를 이용해서 전역설정을 해주었다.
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as cookieParser from 'cookie-parser';
import { HttpExceptionFilter } from 'common/exceptionFilter/http.exception.filter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(cookieParser());
app.useGlobalFilters(new HttpExceptionFilter());
const port = process.env.SERVER_PORT;
await app.listen(port || 3000);
}
bootstrap();
filter파일을 만들고 app.useGlobalFilters(new HttpExceptionFilter())를 적용해 주고 나서 다시한번 같은 에러를 받기위해 일부러 잘못된 정보를 보내보면 내가원한 정상적인 출력을 볼 수 있다.