
Nest에는 애플리케이션 전체에서 처리되지 않은 모든 예외를 처리하는 내장 예외 계층이 있습니다. 애플리케이션 코드에서 예외를 처리하지 못하면 이 계층에서 해당 예외를 포착하여 사용자에게 친숙한 적절한 응답을 자동으로 전송합니다.
기본적으로 이 작업은 내장된 전역 예외 필터 에 의해 수행되며 , 이 필터는 유형 HttpException(및 해당 하위 클래스)의 예외를 처리합니다. 예외가 인식되지 않는HttpException 경우( 또는 를 상속하는 클래스가 아닌 경우 HttpException), 내장된 예외 필터는 다음과 같은 기본 JSON 응답을 생성합니다.
Nest는 패키지 HttpException에 내장된 클래스를 제공합니다 @nestjs/common. 일반적인 HTTP REST/GraphQL API 기반 애플리케이션의 경우, 특정 오류 발생 시 표준 HTTP 응답 객체를 전송하는 것이 가장 좋습니다.
예를 들어, 에 메서드( 라우트 핸들러) CatsController가 있습니다 . 이 라우트 핸들러가 어떤 이유로든 예외를 발생시킨다고 가정해 보겠습니다. 이를 보여주기 위해 다음과 같이 하드코딩합니다.
// cats.controller.ts
import { Controller, Get, Param, NotFoundException } from '@nestjs/common';
import { CatsService } from './cats.service';
@Controller('cats')
export class CatsController {
constructor(private readonly catsService: CatsService) {}
@Get(':id')
async findOne(@Param('id') id: string) {
const cat = await this.catsService.findById(id);
if (!cat) {
throw new NotFoundException(`고양이 ID ${id}를 찾을 수 없습니다.`);
}
return cat;
}
}
생성자 HttpException는 응답을 결정하는 두 가지 필수 인수를 사용합니다.
기본적으로 JSON 응답 본문에는 두 가지 속성이 포함됩니다.
두 번째 생성자 인수 - status는 유효한 HTTP 상태 코드여야 합니다. .@nestjs/common HttpStatus에서 가져온 열거형을 사용하는 것이 가장 좋습니다.
세 번째 생성자 인수(선택 사항) 인 options-HttpException 는 오류 원인을 제공하는 데 사용할 수 있습니다 . 이 cause객체는 응답 객체로 직렬화되지 않지만, 로깅 목적으로 유용할 수 있으며, 오류 발생의 원인이 된 내부 오류에 대한 중요한 정보를 제공합니다 .
import { HttpException, HttpStatus } from '@nestjs/common';
throw new HttpException(
{
statusCode: HttpStatus.BAD_REQUEST,
message: '입력한 값이 유효하지 않습니다.',
error: 'Bad Request',
details: {
field: 'email',
issue: '이메일 형식이 아닙니다.',
},
},
HttpStatus.BAD_REQUEST,
{
cause: new Error('ValidationError: 이메일 형식 오류'),
},
);
response (첫 번째 인수)string 또는 object{
"statusCode": 400,
"message": "입력한 값이 유효하지 않습니다.",
"error": "Bad Request",
"details": {
"field": "email",
"issue": "이메일 형식이 아닙니다."
}
}
details)를 넣어서 응답을 세부적으로 제어할 수 있습니다.status (두 번째 인수)number (HTTP 상태코드)HttpStatus.BAD_REQUEST는 400이며, Nest에서는 @nestjs/common에서 제공하는 HttpStatus enum을 사용하는 것이 좋습니다.options (세 번째 인수) - 선택{ cause?: Error }cause)을 추적하거나 에러 스택을 기록하는 데 유용합니다.예를 들어, 사용자가 이메일 필드에 잘못된 값을 보냈을 때, 아래와 같이 사용할 수 있습니다:
if (!isValidEmail(input.email)) {
throw new HttpException(
{
statusCode: 400,
message: '입력한 이메일이 유효하지 않습니다.',
error: 'Bad Request',
details: {
field: 'email',
issue: '형식이 이메일이 아닙니다.',
},
},
HttpStatus.BAD_REQUEST,
{
cause: new Error('이메일 정규표현식 검사 실패'),
},
);
}
이렇게 하면 프론트엔드에서는 구체적인 오류 메시지를 기반으로 사용자에게 피드백을 줄 수 있고, 백엔드에서는 cause를 기반으로 로깅이나 디버깅이 용이해집니다.
기본적으로 예외 필터는 (해당 필터에서 상속되는 모든 예외)와 같은 내장 예외를 기록하지 않습니다 . 이러한 예외가 발생하면 일반 애플리케이션 흐름의 일부로 처리되므로 콘솔에 표시되지 않습니다.WsExceptionRpcException 및 HttpException와 같은 다른 내장 예외에도 동일한 동작이 적용됩니다 .
@nestjs/common IntrinsicException예외는 모두 패키지 에서 내보내는 기본 클래스를 상속합니다 . 이 클래스는 일반적인 애플리케이션 동작에 포함되는 예외와 그렇지 않은 예외를 구분하는 데 도움이 됩니다.
이러한 예외를 기록하려면 사용자 지정 예외 필터를 만들 수 있습니다. 다음 섹션에서 이 작업을 수행하는 방법을 설명하겠습니다.
대부분의 경우 사용자 지정 예외를 작성할 필요가 없으며, 기본 Nest HTTP 예외를 사용할 수 있습니다. 사용자 지정 예외를 생성해야 하는 경우, 사용자 지정 예외가 기본 클래스를 상속하는 자체 예외 계층 구조를 HttpException으로 만드는 것이 좋습니다 . 이 방법을 사용하면 Nest가 예외를 인식하고 오류 응답을 자동으로 처리합니다. 이러한 사용자 지정 예외를 구현해 보겠습니다.
export class ForbiddenException extends HttpException {
constructor() {
super('Forbidden', HttpStatus.FORBIDDEN);
}
}
Nest는 기본 .NET Framework에서 상속되는 표준 예외 집합을 제공합니다 HttpException. 이러한 예외는 @nestjs/common패키지에 포함되어 있습니다.
기본(내장) 예외 필터는 여러 사례를 자동으로 처리할 수 있지만, 예외 계층을 완벽하게 제어 하고 싶을 수도 있습니다. 예를 들어, 로깅을 추가하거나 일부 동적 요소에 따라 다른 JSON 스키마를 사용하고 싶을 수 있습니다. 예외 필터는 바로 이러한 목적을 위해 설계되었습니다. 이를 통해 클라이언트로 전송되는 응답의 내용과 제어 흐름을 정확하게 제어할 수 있습니다.
클래스의 인스턴스인 예외를 포착하고, 이에 대한 사용자 지정 응답 로직을 구현하는 예외 필터를 만들어 보겠습니다 . 이를 위해서는 기본 플랫폼 과 HttpException 객체 에 접근해야 합니다 . 객체에 접근하여 원본 객체를 추출 하고 로깅 정보에 포함할 것입니다. 또한, 이 객체를 사용하여 메서드를 통해 전송되는 응답을 직접 제어합니다.
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
Logger,
} from '@nestjs/common';
import { Request, Response } from 'express';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
private readonly logger = new Logger(HttpExceptionFilter.name);
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
const exceptionResponse = exception.getResponse();
// 예외 로깅
this.logger.error(
`[${request.method}] ${request.url} ${status} - ${JSON.stringify(exceptionResponse)}`,
);
// 클라이언트에게 반환
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
...(typeof exceptionResponse === 'string'
? { message: exceptionResponse }
: exceptionResponse),
});
}
}
@Post()
@UseFilters(new HttpExceptionFilter())
async create(@Body() createCatDto: CreateCatDto) {
throw new ForbiddenException();
}
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './filters/http-exception.filter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(3000);
}
bootstrap();
import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_FILTER,
useClass: HttpExceptionFilter,
},
],
})
export class AppModule {}
모든 예외를 처리할 때에는 @Catch를 비워두세요
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 {
// In certain situations `httpAdapter` might not be available in the
// constructor method, thus we should resolve it here.
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);
}
}