이번에 사이드 프로젝트로만 경험해본 nestjs를 신규 프로젝트에 사용 중에 있습니다.
프로젝트 중간중간 정리 및 공유하기 좋겠다 싶은 내용들을 정리하는 시리즈 입니다.
nestjs는 기본적으로 응용 프로그램 전반에 걸쳐 처리되지 않는 모든 예외를 처리하는 exceptions layer가 내장되어 있습니다. 코드에서 따로 예외를 처리하지 않았을 때 이 layer가 에러를 핸들링하여 HttpException
타입의 예외를 처리해줍니다.
{
"statusCode": 500,
"message": "Internal server error"
}
또한 @nestjs/common
package를 통해 아래 HttpException
를 상속받은 여러 표준 HTTP 응답 에러 class를 제공합니다.
예를 들어 클라이언트에 400 에러를 전달해야되면 BadRequestException
를 아래와 같이 사용하면 됩니다.
@Get()
async findAll() {
throw new BadRequestException();
}
그럼 클라이언트는 아래와 같은 400 에러 메세지를 전달받을 수 있습니다.
{
"statusCode": 400,
"message": "Bad Request"
}
Nestjs에서 제공해주는 예외 처리 방식을 그대로 사용할 수 있지만, 로깅 또는 sentry 또는 slack 알림 등 처리를 위해 Exception filters
를 통해 커스티마이징을 진행할 수 있습니다.
다음은 공식문서에서 제공해주는 exeception filters 예제입니다.
// 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();
response
.status(status)
.json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}
우선 @Catch(HttpException)
데코레이터는 해당 filter가 HttpException
에 속하는 에러가 있는지 체크를 진행합니다.(해당 체크에 통과된 에러를 filter 진행합니다.)
@Catch
는 파라미터가 하나일 수도 있고 콤마로 여러 개를 구분할 수 있습니다.
// @nestjs/common에 구현된 @Catch
export declare function Catch(
...exceptions: Array<Type<any> | Abstract<any>>
): ClassDecorator;
HttpExceptionFilter class안의 catch의 파라미터를 살펴보겠습니다.
exception
는 현재 처리되고 있는 에러의 object 입니다. host
는 ArgumentsHost는 request handler를 통해 통과된 response
와 request
를 가져오기 위해 사용되었습니다.
// ArgumentsHost의 switchToHttp 함수가 반환하는 타입
export interface HttpArgumentsHost {
/**
* Returns the in-flight `request` object.
*/
getRequest<T = any>(): T;
/**
* Returns the in-flight `response` object.
*/
getResponse<T = any>(): T;
getNext<T = any>(): T;
}
catch에 프로젝트에 따라 로깅이나, slack 알림 명령어 등을 추가할 수 있습니다.
위 예제로 진행하는 경우 @Catch
를 통해 체크되는 예외만 전달되게 됩니다.
모든 에러를 핸들링하고 싶은 경우 아래와 같이 변경을 진행하면 됩니다.
// Catch에 아무런 값도 넣지 않는다.
@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
// exception을 unknown 으로 변경
catch(exception: unknown, host: ArgumentsHost) {
const httpStatus =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
// 생략...
위에서 새로 만든 Filter를 적용하는 방법은 아래와 같습니다.
@Post()
@UseFilters(HttpExceptionFilter)
async create(@Body() createCatDto: CreateCatDto) {
throw new ForbiddenException();
}
우선 @UseFilters
를 통해 특정 api만 적용을 할 수 있습니다.
또한 controller에 적용을 할 수 있습니다.
@UseFilters(new HttpExceptionFilter())
export class CatsController {}
애플리케이션 전역에서 작동을 할 수 있도록 global-scoped filter
를 설정할 수 있습니다. main.ts파일 안에 아래 예제와 같이 useGlobalFilters
를 통해 설정할 수 있습니다.
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(3000);
}
bootstrap();
Global-scoped filters
는 모든 컨트롤러 및 모든 경로 처리기에 대해 전체 응용 프로그램에 걸쳐 사용됩니다. 종속성 주입 측면에서 임의의 모듈 외부에서 등록된 전역 필터는 종속성을 주입할 수 없습니다. 그렇기에 아래와 같이 직접 Global-scoped filters
를 등록을 필요로 합니다.
//app.module.ts
import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_FILTER,
useClass: HttpExceptionFilter,
},
],
})
export class AppModule {}