
Nest는 애플리케이션 전체의 모든 unhandled exceptions를 처리하는 exceptions layer가 내장되어 있다. 따로 예외처리를 하지 않으면 이 expcetions layer에서 예외를 잡고 자동으로 적절한 response를 보내준다.

이 작업은 기본적으로 HttpException 유형의 예외를 처리하는 global exception filter에 의해 수행된다. 예외가 인식되지 않는 경우(HttpException, HttpException을 상속하는 클래스도 아닌 경우) exception filter는 이런 기본값 response를 보낸다.
{
"statusCode": 500,
"message": "Internal server error"
}
Nest는 @nestjs/common 패키지에서 expose하는 HttpException 클래스를 제공한다. 일반적인 HTTP REST/GraphQL API 기반 응용 프로그램의 경우 특정 오류 조건이 발생할 때 표준 HTTP 응답을 보내는 것이 좋다.
예를 들어, CatsController에는 findAll()이 있다. 이 라우트 핸들러는 어떤 이유로 예외를 던졌다고 가졍해보자.
@Get()
async findAll() {
throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}
response는 이렇다.
{
"statusCode": 403,
"message": "Forbidden"
}
HttpException constructor는 두 개의 필수 인자를 사용하여 응답을 결정한다.
response는 JSON body 형태로 되어있으며 string이나 object일 수 있다.status는 HTTP status code로 정의한다.기본값으로 JSON response body에는 두 가지 property가 있다.
statusCode: 기본 값은 상태 인수에 제공된 HTTP 상태 코드이다.message: 상태에 따른 HTTP 오류에 관한 간단한 설명이다.JSON response body를 overrid하려면 response argument를 제공하면 된다. Nest는 객체를 serialize해 JSON response body를 return한다.
두 번째 constructor 인수인 status는 유효한 HTTP status code여야 한다. @nestjs/common의 HttpStatus enum을 import해서 사용하는 것이 좋다.
아래는 response body를 overriding하는 예시이다.
@Get()
async findAll() {
try {
await this.service.findAll()
} catch (error) {
throw new HttpException({
status: HttpStatus.FORBIDDEN,
error: 'This is a custom message',
}, HttpStatus.FORBIDDEN, {
cause: error
});
}
}
이렇게 하면 이런 식의 response를 받을 수 있다.
{
"status": 403,
"error": "This is a custom message"
}
Nest에서는 대부분의 경우 custom exception을 쓸 필요가 없다. Nest에 내장된 HTTP exception을 사용하면 된다. 만약 정말 필요하면, HttpException 클래스로부터 상속받는 고유한 exception layer를 만드는 것이 좋다. 이 방식을 사용하면 Nest가 예외를 인식하고 자동으로 error response를 처리한다.
export class ForbiddenException extends HttpException {
constructor() {
super('Forbidden', HttpStatus.FORBIDDEN);
}
}
ForbiddenException이 HttpException을 상속받기 때문에, 내장 Exception handler가 원활하게 작동하므로 사용할 수 있다.
@Get()
async findAll() {
throw new ForbiddenException();
}
Nest는 기본적으로 HttpException에서 상속되는 표준 예외를 제공한다. @nestjs/common 패키지에서 expose 되며 가장 일반적인 HTTP 예외를 나타낸다.

모든 built-in exceptions는 options 매개변수를 사용해 오류 원인과 설명을 모두 제공할 수 있다.
throw new BadRequestException('Something bad happened', { cause: new Error(), description: 'Some error description'});
그럼 이렇게 보일 것이다.
{
"message": "Something bad happened",
"error": "Some error description",
"statusCode": 400,
}
기본적으로 내장된 exception filter가 자동으로 많은 예외를 처리해주지만, exception layer에 대한 완전히 제어하고 싶을 수 있다. 예를 들어, log를 추가하거나 여러 동적인 요인을 기반으로 다른 JSON schema를 사용하고 싶은 경우이다(?) 이런 목적을 위해 고안한 것이 Excpetion filter이다. 이는 클라이언트에게 보내지는 응답의 내용이나 제어의 정확한 흐름을 제어할 수 있게 한다.
HttpException 클래스의 인스턴스인 예외를 포착하고 커스텀 respons 로직을 구현하는 예외 필터를 만들어보자. 이걸 위해서 우리는 기본 플랫폼의 Request와 Response 객체에 접근해야 한다. 우리는 원본 url을 꺼내 로깅 정보에 포함할 수 있도록 Request객체에 접근할 것이다. response.json()을 사용해, Response 객체를 사용해 전송된 response를 직접 제어할 것이다.
import { ExceptionFilter, Catch, ArgumentHost, 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 filter는
ExceptionFilter<T>인터페이스를 implement해야 한다. 이를 위해서는catch(exception: T, host: ArgumentsHost)메서드를 필요로 한다. T는 예외 유형을 나타낸다.
@Catch(HttpException) 데코레이터는 필요한 메타데이터를 exception filter에 바인딩하여 이 특정 필터가 Nest에게 HttpException 유형의 예외를 찾고 있다고 말해준다. @Catch() 데코레이터는 단일 매개변수 또는 쉼표로 구분된 목록을 사용할 수 있다. 이를 통해 한 번에 여러 유형의 예외에 대한 필터를 설정할 수 있다.
catch()의 매개변수를 보면 exception 매개변수는 현재 처리 중인 exception object이고, host 매개변수는 ArgumentsHost object이다. 위의 코드에서 우리는 이것을 원래의 request handler에서 통과된 Request와 Response object의 레퍼런스를 가져오기 위해 사용했다. ArgumentsHost는 execution context에서 더 자세하게 다룬다고 한다.
일단 모르겠으니 나중에 다시 보자...
이제 새 HttpExceptionFilter를 CatsController의 create()에 연결해보자.
@Post()
@UseFilters(new HttpExceptionFilter())
async create(@Body createCatDto: CreateCatDto) {
throw new ForbiddenException();
}
위 코드는 HttpExceptionFilter의 인스턴스를 만들었다.
하지만 대신, class를 넣으면 인스턴스화의 책임을 회피하면서 dependency injection을 가능하게 할 수 있다.
@Post()
@UseFilters(HttpExceptionFilter)
async create(@Body createCatDto: CreateCatDto) {
throw new ForbiddenException();
}
가능하면 인스턴스 대신 클래스를 사용해 필터를 적용하자. Nest는 전체 모듈에서 동일한 클래스의 인스턴스를 쉽게 재사용할 수 있으므로 메모리 사용량을 줄여준다.
위의 예시는 단일 create() route에만 HttpExceptionFilter가 적용되어 method-scoped이다. 예외 필터는 method-scoped, controller-scoped, global-scoped가 될 수 있다.
@UseFilters(new HttpExceptionFilter())
export class CatsController {}
위처럼 쓰면 HttpExceptionFilter가 CatsController의 모든 라우터에 세팅된다.
global-scoped 필터를 쓰고 싶다면 아래처럼 쓰자.
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(3000);
}
bootstrap();
종속성 주입 측면에서 모든 모듈 외부에서 등록된 global filter는 모듈의 context 외부에서 수행되기 때문에 의존성 주입이 불가능해진다. 해당 이슈를 해결하기 위해 다음처럼 사용해 글로벌 범위 필터를 등록할 수 있다.
import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_FILTER,
useClass: HttpExceptionFilter,
},
],
});
만약 모든 unhandled exception을 catch하고 싶다면, 파라미터를 비운 채 Catch() 데코레이터를 사용하면 된다.
일반적으로 우리는 애플리케이션 요구사항을 완전히 충족할 수 있도록 완전히 customize된 exception filter를 만들 것이다. 그러나 단순히 내장된 global exception filter를 extned하고 특정 상황에 따라 어떤 기능을 override하고 싶을 수도 있다.
base filter에 예외처리를 위임하기 위해서 BaseExceptionFilter를 extned 한 뒤 상속된 catch()를 call해야 한다.
import { Catch, ArgumentHost } from '@nestjs/common';
import { BaseExceptionFilter } from '@nestjs/core';
@Catch()
export class AllExceptionsFilter extends BaseExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
super.catch(exception, host);
}
}