HTTP 요청에 대해서 서버가 이를 처리할 때 오류가 의도치 않게 또는 의도해서 발생하는 경우가 있다. exception filter는 오류가 발생할 때 어떻게 처리를 할지 결정하는 클래스이다.
exception filter는 예외 발생 처리를 목적으로 한다. 만약, 클라이언트가 서버가 다루지 않는 특정 주소로 HTTP 요청을 보냈다면 nest에 내장된 글로벌 exception filter를 통해 아래와 같이 404 응답을 받을 것이다.
{
"statusCode" : 404,
"message" : "Cannot GET /abcde",
"error": "Not Found"
}
위의 예시와 다르게 로직 흐름 상 직접 예외를 발생 시켜야하는 순간이 있다. 이 때 HttpException
의 인스턴스를 throw하면 내장된 글로벌 필터가 이를 처리해서 일정한 형식의 응답을 반환해준다.
@Get()
getAllCat(){
throw new HttpException('bad request', HttpStatus.BAD_REQUEST)
}
HttpException
의 첫 인자에는 메세지가 들어간다. 문자열 형태나 객체 형태 모두 허용하며 자동으로 직렬화된다. 두번째 인자는 응답코드를 의미한다.
위에 명시된 getAllCat 함수가 처리해야하는 요청이 온다면 아래와 같이 메세지는 bad request
가 될 것이고 응답코드는 400이 될 것이다.
{
"statusCode" : 400,
"message" : "bad request"
}
nest는 매번 응답코드를 입력할 필요없이 미리 코드가 지정된 HttpException
를 상속한 내장 클래스들을 제공한다. 주로 사용하는 클래스는 아래와 같다.
1. BadRequestException
2. UnauthorizedException
3. NotFoundException
4. ForbiddenException
5. ConflictException
원한다면 직접 HttpException
을 상속하는 예외를 만들 수 있다. 아래의 예시는 forbidden.exception.ts에 403 응답코드를 반환하는 예외 클래스를 정의한 것이다.
export class ForbiddenException extends HttpException {
constructor(){
super('Forbidden', HttpStatus.FORBIDDEN);
}
}
HttpException
을 커스텀했던 것처럼 exception filter도 커스텀이 가능하다.
//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
})
}
}
우선, 어떤 Exception이 발생했는지에 따라 동작을 할지 결정할 수 있도록 @Catch
에 Exception을 명시한다.
두번째로, ExceptionFilter를 implements한 필터 클래스를 정의한다. 이 클래스에 catch라는 함수만 오버라이딩해주면 된다. 첫번째 인자는 말 그대로 발생한 예외를 받고, 두번째 인자는 예외가 발생한 시점에서 response와 request를 받기 위해서 필요한 호스트다. 이를 통해서 response의 반환값에 개입을 할 수 있다.
커스텀 필터를 만들면 이제 어디에 적용을 할 지 지정해야한다. 지정할 수 있는 곳으로는,
1. HTTP 요청 핸들러
2. Controller
3. Main
으로, 1번 2번은 범위를 따지면 로컬이고 3번은 글로벌이다. 로컬의 경우에는 @UseFilters
데코레이터를 이용해 등록하고 싶은 클래스나 핸들러 함수 위에 적어주면 된다.
// HTTP 요청 핸들러에 필터 적용
@Post()
@UseFilters(HttpExceptionFilter)
async create(@Body() createCatDto: CreateCatDto) {
throw new ForbiddenException();
}
// Controller 전체에 적용
@UseFilters(HttpExceptionFilter)
export class CatsController{}
3번의 경우 Main.ts의 bootstrap 함수에 등록을 하면 된다.
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(8000);
}
bootstrap();
주의할 점은 1번, 2번의 경우에는 DI 덕분에 커스텀 필터의 인스턴스를 넘겨줄 필요없이 클래스만 넘겨주면 된다. 3번의 경우에는 인스턴스만 넘겨주는 것이 가능하다. DI를 고려해서 main.ts가 아닌 모듈에 따로 등록하는 방법은 있지만 일단 생략한다.