일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
- java
- spring security
- factory
- Google OAuth
- Volatile
- OAuth 2.0
- Dependency Injection
- builder
- 일급 컬렉션
- 일급 객체
- Spring
- lombok
- middleware
- nestjs
- synchronized
- Today
- Total
HJW's IT Blog
NestJS Document 알아보기 : Exception Filters 본문
Exciption Filters
NestJS 는 어플리케이션 전반에서 처리되지 않은 예외를 처리하는 예외 계층을 제공한다. 즉, 코드상에서 예외가 처리되지 않았을 경우, 해당 계층에서 예외를 감지하고 적절한 응답을 자동으로 전송한다.
기본적으로 NestJS 는 HttpException
및 그 하위 클래스에 대한 예외 처리를 제공하는 글로벌 예외 필터를 내장하고 있다. 인식되지 않은 예외가 발생할 시, statusCode : 500 과 messate: internal server error 를 반환한다.
Throwing Standard Exceptions
NestJS 는 @nestjs/common
패키지에서 제공하는 HttpException
클래스를 통해 표준화된 HTTP 응답을 보낼 수 있다.
다음은 CatsController
에서 findAll()
메서드가 예외를 발생시키는 예시이다
@Get()
async findAll(){
throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}
// 응답
{
"statusCode" : 403,
"message" : "Forbidden"
}
Customize Exception Response
NestJS 에서는 응답 본문을 덮어쓸 수 있는데, 다음은 그 예시이다.
@Get()
async findAll(){
try{
await this.service.findAll();
}catch(error){
throw new HttpException({
status: HttpStatus.FORBIDDEN,
error: 'This is custom message.',
}, HttpStatus.FORBIDDEN, {
cause: error,
});
}
}
// 응답
{
"status": 403,
"error": "This is a custom message"
}
Custom Exceptions
NestJS 에서 커스텀 예외를 작성해야 할 경우, HttpException
을 상속받아 예외 계층을 만드는 것이 권장된다. 상속받아 예외 계층을 생성할 경우, Nest 는 커스텀 예외를 자동으로 인식하고 자동으로 에러 응답 처리를 해준다. 다음은 커스텀 예외를 구현하는 예시이다
// forbidden.exception.ts
import { HttpException, HttpStatus } from '@nestjs/common';
export class ForbiddenException extends HttpException {
constructor() {
super('Forbidden', HttpStatus.FORBIDDEN);
}
}
이 ForbiddenException
은 기본 HttpException
을 상속받기 때문에 Nest의 내장 예외 처리기와 원활하게 작동한다. 이를 컨트롤러 메서드에서 다음과 같이 사용할 수 있다.
// cats.controller.ts
@Get()
async findAll() {
throw new ForbiddenException();
}
Exception Filter
기본 예외 필터는 대부분의 경우 자동으로 예외 처리를 하지만, 예외 처리 흐름을 완전해 제어하고 싶을 때는 커스텀 예외 필터를 사용할 수 있다. 예를 들어 로그를 추가하거나, 특정 상황에 따라 JSON 응답 구조를 다르게 하고 싶다면, 예외 필터를 사용하면 된다.
다음은 HttpException
을 처리하고 커스텀 응답 로직을 구현하는 예외 필터의 예시이다
// http-exception.filter.ts
import {ExceptionFilter, Catch, ArugumentHost, HttpException} from '@nestjs/common'
import {Request, Response} form '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)
데토레이터는 해당 필터가 HttpException 타입의 예외를 처리하도록 지정한다. 여러 타입의 예외를 처리하고 싶다면, @Catch()
에 여러 예외 타입을 쉼표로 구분하여 전달할 수 있다.
Argument Host
ArgumentHost
는 NestJS 의 강력한 utility 객체로, catch() 메서드에서 사용되는 두번째 인자이다. 해당 객체는 예외가 발생한 컨텍스트에 관계없이 적절한 요청 관련 데이터를 가져올 수 있도록 추상화된 계층을 제공한다. 예외 필터에서 해당 객체를 사용하여 원래 요청 처리에서 사용되는 Request
와 Response
객체에 접근 가능하다. 위의 예시에서 볼 수 있다.
ArgumentHost
는 단순히 HTTP 컨텍스트에만 적용되는 것이 아니라, 다양한 실행 컨텍스트에서 사용할 수 있는 공통 인터페이스이다. 각 다른 컨텍스트에 따라 적절한 객체를 가져올 수 있으며, 이를 통해 모든 컨텍스트에서 작동하는 범용 예외 필터를 작성할 수 있다.
Binding Filters
NestJS 에서는 @UseFilters()
를 통해 특정 메서드, 컨트롤러에 예외 필터를 적용할 수 있다.
Method Scope Filter
특정 라우트 핸들러에만 예외 필터를 적용하려면 다음과 같이 적용하면 된다.
// cats.controller.ts
@Post()
@UseFilters(new HttpExceptionFilter())
async create(@Body() createCatDto: CreateCatDto) {
throw new ForbiddenException();
}
Class Scope Filter
// cats.controller.ts
@UseFilters(new HttpExceptionFilter())
export class CatsController {
// 이 컨트롤러의 모든 핸들러에 필터가 적용됨
@Post()
async create(@Body() createCatDto: CreateCatDto) {
throw new ForbiddenException();
}
}
Global Scope Filter
// main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
Global Filter DI
전역 필터에서 의존성 주입을 사용해야 할 경우, 필터를 useGlobalFilters()
로 등록하는 대신, 모듈의 providers
배열을 통해 등록이 가능하다.
이 방법을 사용하면 의존성 주입을 활용하며, 필터를 글로벌 스코프로 적용할 수 있다.
// app.module.ts
import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';
import { HttpExceptionFilter } from './filters/http-exception.filter';
@Module({
providers: [
{
provide: APP_FILTER,
useClass: HttpExceptionFilter,
},
],
})
export class AppModule {}
Catch Everything
NestJS에서 모든 처리되지 않은 예외를 포착하려면, @Catch()
데코레이터의 파라미터 리스트를 비워둔다. 이렇게 하면 예외 타입에 상관없이 모든 예외를 처리할 수 있다. 아래의 예시는 플랫폼에 종속되지 않은 방식으로 예외를 처리하며, HTTP 어댑터를 사용하여 응답을 반환한다.
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus,
} from '@nestjs/common';
import { HttpAdapterHost } from '@nestjs/core';
@Catch()
export class CatchEverythingFilter implements ExceptionFilter {
constructor(private readonly httpAdapterHost: HttpAdapterHost) {}
catch(exception: unknown, host: ArgumentsHost): void {
// 특정 상황에서는 httpAdapter가 생성자에서 사용할 수 없기 때문에 여기서 해결해야 함
const { httpAdapter } = this.httpAdapterHost;
const ctx = host.switchToHttp();
const httpStatus =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const responseBody = {
statusCode: httpStatus,
timestamp: new Date().toISOString(),
path: httpAdapter.getRequestUrl(ctx.getRequest()),
};
httpAdapter.reply(ctx.getResponse(), responseBody, httpStatus);
}
}
이 필터는 모든 예외를 처리하며, HttpException
인지 여부에 따라 HTTP 상태 코드를 결정하고, 그렇지 않다면 기본적으로 500 내부 서버 오류(INTERNAL_SERVER_ERROR
)를 반환한다.
경고: 예외 필터 우선순위
모든 예외를 처리하는 필터(Catch anything
필터)를 특정 예외에만 바인딩된 필터와 함께 사용할 경우, 포괄적인 필터가 먼저 선언되어야 한다. 그렇지 않으면 특정 타입에 바인딩된 필터가 제대로 동작하지 않을 수 있다.
상속을 통한 기본 예외 필터 확장
때로는 기본 글로벌 예외 필터를 확장하여 특정 조건에 따라 동작을 덮어쓰고 싶을 수 있다. 이 경우, BaseExceptionFilter
를 상속받아 catch()
메서드를 재정의하여 원하는 동작을 추가하면서도 기본 동작을 유지할 수 있다.
import { Catch, ArgumentsHost } from '@nestjs/common';
import { BaseExceptionFilter } from '@nestjs/core';
@Catch()
export class AllExceptionsFilter extends BaseExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
super.catch(exception, host); // 기본 필터의 동작을 호출
}
}
이렇게 하면, 기본 예외 처리 로직을 그대로 사용하면서 특정 예외에 대해 추가적인 동작을 수행할 수 있다.
전역 필터로 등록
글로벌 필터를 확장하고 이를 애플리케이션 전체에 적용하려면, 아래와 같이 HttpAdapter
를 주입하여 필터를 인스턴스화할 수 있다.
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const { httpAdapter } = app.get(HttpAdapterHost);
app.useGlobalFilters(new AllExceptionsFilter(httpAdapter));
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
이 방식은 애플리케이션 전체에서 예외를 처리할 때 사용되며, 글로벌 스코프에서 모든 예외에 대해 처리할 수 있게 한다.
'NestJS' 카테고리의 다른 글
NestJS Document 알아보기 : Middleware (0) | 2024.10.20 |
---|---|
NestJS Document 알아보기 : Module (3) | 2024.10.20 |
NestJS Document 알아보기 : Providers (1) | 2024.10.19 |
NestJS Document 알아보기 : Controller (1) | 2024.10.19 |