개발한지 얼마 되지 않은 주니어 개발자들이 가장 많이 실수하는게, 예외처리를 꼼꼼히 하지 않는다는 점이다. 모든것은 예외가 발생할 수 있고 당장 잘되던 것도 사용자가 늘어나거나 유지보수가 진행됌에 따라 얼마든지 예외가 발생하는 법이다. 따라서 예상되거나 발생하면 치명적인 예외는 꼼꼼히 작성해주는 것이 좋다.
throw new HttpException(
{
errorMessage: 'id는 0보다 큰 정수여야 합니다',
foo: 'bar'
},
HttpStatus.BAD_REQUEST
);
{
"errorMessage":"id는 0보다 큰 정수여야 합니다",
"foo":"bar"
}
NestJS에서는 위 처럼 Exception을 직접 정의해서 전달해도 되지만, 표준 예외들 또한 정의되어 있어 간편하게 쓸 수 있다. 자주 사용하는건 ★를 메모해두었다.
Nest기본 예외의 구조를 보면 다음과 같은 형태를 가진다.
export class BadRequestException extends HttpException {
constructor(
objectOrError?: string | object | any,
description = 'Bad Request',
) {
super(
HttpException.createBody(
objectOrError,
description,
HttpStatus.BAD_REQUEST,
),
HttpStatus.BAD_REQUEST,
);
}
}
생성자를 보면 2개의 인자를 받는데 다음처럼 메세지와 error를 변환해서 출력할 수 있다.
throw new BadRequestException('id는 0보다 큰 정수여야 합니다', 'id format exception');
{
"statusCode": 400,
"message": "id는 0보다 큰 정수여야 합니다",
"error": "id format exception"
}
Nest에서 제공하는 기본 예외 필터외에 직접 예외 필터를 두어 원하는 대로 예외를 다루어 볼 것이다.
다음코드는 HttpException이 아닌 알 수 없는 예외를 처리하기 위한 필터로 예외 발생시 예외가 발생한 url, 시간, 응답객체를 콘솔에 출력하는 필터를 만든 것이다.
import { MyLogger } from '@config/logger.config';
import {
ArgumentsHost,
Catch,
ExceptionFilter,
HttpException,
Inject,
InternalServerErrorException,
} from '@nestjs/common';
import { Request, Response } from 'express';
@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: Error, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const req: Request = ctx.getRequest<Request>();
const res: Response = ctx.getRequest<Response>();
if (!(exception instanceof HttpException)) {
exception = new InternalServerErrorException();
}
const response = (exception as HttpException).getResponse();
const log = {
timestamp: new Date(),
url: req.url,
response,
};
console.log(log, req.baseUrl);
res.status((exception as HttpException).getStatus()).json(response);
}
}
핵심은 @Catch()
데코레이더이다. Catch()
는 처리되지않은 예외를 잡으려고 할 때 사용한다. 우리가 다루는 대부분의 예외는 HttpException을 상속받은 클래스로 제공된다.
@Controller('users')
export class UsersController {
...
@UseFilters(HttpExceptionFilter)
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
...
}
@Controller('users')
@UseFilters(HttpExceptionFilter)
export class UsersController {
...
}
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpExceptionFilter()); // 전역 필터 적용
await app.listen(3000);
}
예외필터는 모듈(main.ts)외부에서 이루어 지기 때문에 의존성을 주입 받을려면 예외 필터를 커스텀 프로바이더로써 등록해야한다.
import { dataBaseConfig } from '@config/database.config';
import { Module } from '@nestjs/common';
import { AccountModule } from './account/account.module';
import { FileModule } from './file/file.module';
import { CustomConfigModule } from './config.module';
import { AuthGuard } from '@config/guard/jwt.guard';
import { HttpExceptionFilter } from '@config/filters/exception.filter';
@Module({
imports: [CustomConfigModule, AccountModule, FileModule],
providers: [
AuthGuard,
{ provide: 'EXCEPTION_FILTER', useClass: HttpExceptionFilter },
],
exports: [],
})
export class AppModule {}
import { MyLogger } from '@config/logger.config';
import {
ArgumentsHost,
Catch,
ExceptionFilter,
HttpException,
InternalServerErrorException,
} from '@nestjs/common';
import { Request, Response } from 'express';
@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
constructor(private readonly logger: MyLogger) {}
catch(exception: Error, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const req: Request = ctx.getRequest<Request>();
const res: Response = ctx.getRequest<Response>();
if (!(exception instanceof HttpException)) {
exception = new InternalServerErrorException();
}
const response = (exception as HttpException).getResponse();
const log = {
timestamp: new Date(),
url: req.url,
response,
};
this.logger.error(log, req.baseUrl);
res.status((exception as HttpException).getStatus()).json(response);
}
}
이제 일부러 500ERROR를 만들면 다음과 같이 커스텀 예외 필터가 로그에 출력된다.
@Get('/error/:foo')
@UseFilters(HttpExceptionFilter)
@ApiOperation({
summary: '에러 발생기',
description: '예외 필터의 테스트를 위해 만듬',
})
error(foo: any): string {
return foo.bar(1);
}