Nest 에는 애플리케이션 전체에서 처리되지 않은 모든 예외를 처리하는 내장 예외 레이어가 함께 제공된다. 애플리케이션 코드에서 예외가 처리되지 않으면 이 계층에서 이를 포착한 다음 사용자에게 친숙한 적절한 응답을 자동으로 보낸다.
기본적으로 nestjs가 expressjs를 내장하고 있고, express 4버젼 부터는 try-catch를 router에 감싸지 않아도 알아서 예외처리를 해주기 때문에 nest도 이와 같을 거라 생각한다. 이제 router마다 try-catch를 감싸주지 않아도 된다.
export class ForbiddenException extends HttpException {
constructor() {
super('Forbidden', HttpStatus.FORBIDDEN);
}
}
https://docs.nestjs.com/exception-filters#custom-exceptions
nestjs 공식문서에 에러필터를 커스텀하려면 HttpException class를 확장하라고 나와있다.
@Catch(HttpException)
export class ForbiddenException extends HttpException {
constructor() {
super('Forbidden', HttpStatus.FORBIDDEN);
}
}
그리고 위와 같이Catch
annotation을 활용하여 특정 에러에 대해 filter를 적용할 수 있다.
enum CustomErrorCode {
// 인증 관련 에러 코드
INVALID_LOGIN = 1000,
USER_ALREADY_EXIST = 1001, // 이미 존재하는데 회원가입 하는 경우
USER_NOT_AUTHENTICATED = 1002, // 회원가입되지 않은 유저를 로그인 하는 경우
INVALID_NICKNAME = 1100, // 닉네임에 욕설 및 비속어 포함
DUPLICATE_NICKNAME = 1101, // 이미 존재하는 닉네임
// 토큰 관련 코드
NO_ACCESS_TOKEN = 2000,
EXPIRED_ACCESS_TOKEN = 2001,
INVALID_ACCESS_TOKEN = 2002,
NO_REFRESH_TOKEN = 2100,
EXPIRED_REFRESH_TOKEN = 2101,
INVALID_REFRESH_TOKEN = 2102,
// 유저 관련 에러 코드
USER_NOT_FOUND = 3000,
// 선물 관련 에러 코드
PRESENT_NOT_FOUND = 4000,
PRESENT_GOAL_SHORT_FALL = 4001,
PRESENT_DEADLINE_SHORT_FALL = 4002,
PRESENT_NOT_MINE = 4003,
PRESENT_INVALID_DATE = 4004,
// 펀딩 관련 에러 코드
FUNDING_NOT_FOUND = 5000,
FUNDING_MONEY_SHORT_FALL = 5001,
FUNDING_ALREADY_END = 5002,
FUNDING_ALREADY_COMPLETE = 5003,
FUNDING_NOT_MINE = 5004,
// 이미지 관련 에러 코드
IMAGE_NOT_FOUND = 6000,
// request 요청 에러
INVALID_PARAM = 7000,
INVALID_QUERY = 7001,
// validation 에러
VALIDATION_ERROR = 8000,
// 서버 에러
INTERNAL_SERVER_ERROR = 9000,
// NOT FOUND 에러
PAGE_NOT_FOUND = 10000,
// 예상못한 에러
UNCATCHED_ERROR = 99999,
}
export default CustomErrorCode;
먼저 위와 같이 에러 코드를 정의해 주었다.
front에서 처리할 수 있는 에러들에 대해서는 status code외에 code를 따로 보내주기로 하였다.
if (!user) {
throw new BadRequestException({
message: '회원가입 되지 않은 유저입니다!',
code: customErrorCode.USER_NOT_AUTHENTICATED,
});
}
그리고 에러가 났을때 이와 같이 message와 code를 에러 객체 안에 넣어주었다.
@Catch()
export class CustomErrorFilter implements ExceptionFilter {
catch(exception: any, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
}
if (exception instanceof BadRequestException) {
// BadRequest Error
} else if (exception instanceof InternalServerErrorException) {
// 처리할 수 있는 server Error
}
else if (exception instanceof NotFoundException { // NOT FOUND 404 ERROR
}
else if (exception instanceof HttpException) {
// 위에 속하지 않는 HTTP ERROR
}
else {
// FATAL ERROR
}
}
먼저 에러를 크게 다섯가지로 나누었다.
client의 실수로 발생한 BadRequestException
서버측의 에러인 InternalServerErrorException
존재하지 않는 API조회시 발생하는 NotFoundException
이외의 HTTP 예외인 HttpException
그리고 이 모든 에러에 잡히지 않는 에러인 FATALError
로 정의 하였다.
그리고 FATALError
에 대해선 즉각 slack으로 메세지를 보내게 하여 바로 처리할 수 있게 하였다.
providers: [
AppService,
{
provide: APP_FILTER,
useClass: CustomErrorFilter,
},
],
그리고 app.module.ts
의 provider에 우리가 정의해준 CustomErrorFilter파일을 넣어주면 된다.
{
"statusCode": 500,
"message": "UNCATCHED ERROR",
"description": "Cannot read properties of undefined (reading 'accessToken')",
"code": 99999
}
이렇게 하면 Front end는 모든 발생하는 에러에 대해서 똑같은 객체로 값을 받게되고, code로 특정 에러들에 대해 핸들링 할 수 있게 된다.