throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
{
"statusCode": 403,
"message": "Forbidden"
}
new HttpException(response, status, options?)
throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
응답:
{
"statusCode": 403,
"message": "Forbidden"
}
throw new HttpException(
{
status: HttpStatus.FORBIDDEN,
error: 'This is a custom message',
},
HttpStatus.FORBIDDEN,
{
cause: error // 내부 로깅용, 클라이언트에게는 응답되지 않음
}
);
응답:
{
"status": 403,
"error": "This is a custom message"
}
// forbidden.exception.ts
import { HttpException, HttpStatus } from '@nestjs/common';
export class ForbiddenException extends HttpException {
constructor() {
super('Forbidden', HttpStatus.FORBIDDEN);
}
}
// cats.controller.ts
@Get()
async findAll() {
throw new ForbiddenException();
}
응답:
{
"statusCode": 403,
"message": "Forbidden"
}
| 클래스 이름 | 의미 | 상태 코드 |
|---|---|---|
BadRequestException | 잘못된 요청 | 400 |
UnauthorizedException | 인증 실패 | 401 |
ForbiddenException | 접근 금지 | 403 |
NotFoundException | 리소스 없음 | 404 |
ConflictException | 리소스 충돌 | 409 |
InternalServerErrorException | 서버 에러 | 500 |
ServiceUnavailableException | 서비스 사용 불가 | 503 |
ImATeapotException | 유머용 상태 코드 🍵 | 418 |
| ⋯ 등 총 20여 개 예외를 제공 |
throw new NotFoundException('User not found');
응답:
{
"statusCode": 404,
"message": "User not found"
}
throw new BadRequestException('Something bad happened', {
cause: new Error(),
description: 'Some error description',
});
| 항목 | 설명 |
|---|---|
'Something bad happened' | 기본 메시지 (message 필드에 들어감) |
cause | 내부 로깅이나 추적용 원인 예외 (응답에는 포함되지 않음) |
description | 사용자 정의 상세 설명 (error 필드에 포함됨) |
응답:
{
"message": "Something bad happened",
"error": "Some error description",
"statusCode": 400
}
NestJS의 기본 예외 필터는 HttpException 기반 예외에 대해 잘 작동하지만, 다음과 같은 요구가 있을 때는 직접 예외 필터를 구현해야 한다
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp(); // HTTP 컨텍스트 추출
const response = ctx.getResponse<Response>(); // Express의 Response 객체
const request = ctx.getRequest<Request>(); // Express의 Request 객체
const status = exception.getStatus(); // 상태코드 추출
// 로깅 서비스 이용 등...
response.status(status).json({ // 직접 응답 구성
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}
| 구성 요소 | 설명 |
|---|---|
@Catch(HttpException) | 이 필터는 HttpException만 처리함 |
ExceptionFilter<T> | Nest의 예외 필터 인터페이스. T는 처리할 예외 타입 |
catch() | 예외가 발생했을 때 호출되는 메서드 |
ArgumentsHost | 현재 실행 컨텍스트(HTTP, WebSocket 등)를 제공 |
switchToHttp() | HTTP 컨텍스트로 전환 |
getRequest() / getResponse() | Express의 Request/Response 객체 추출 |
response.status().json() | 직접 응답을 작성 (상태 코드, 경로, 시간 포함) |
@Post()
@UseFilters(HttpExceptionFilter)
async create(@Body() createCatDto: CreateCatDto) {
throw new ForbiddenException();
}
@Controller()
@UseFilters(HttpExceptionFilter)
export class CatsController {
@Post()
async create(@Body() createCatDto: CreateCatDto) {
throw new ForbiddenException();
}
@Get()
async findAll() {
throw new ForbiddenException();
}
}
// main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpExceptionFilter()); // 전역 필터 등록
await app.listen(3000);
}
@Module({
providers: [
{
provide: APP_FILTER,
useClass: HttpExceptionFilter,
},
],
})
export class AppModule {}
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 {
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);
}
}
응답:
// ex)
const responseBody = {
statusCode: 500,
timestamp: new Date().toISOString(),
path: httpAdapter.getRequestUrl(ctx.getRequest()),
};
httpAdapter.reply(ctx.getResponse(), responseBody, 500);
->
{
"statusCode": 500,
"timestamp": "2025-05-08T06:30:45.123Z",
"path": "/users"
}
import {
Catch,
ArgumentsHost,
HttpException,
Logger,
} from '@nestjs/common';
import { BaseExceptionFilter } from '@nestjs/core';
import { Request } from 'express';
import * as Sentry from '@sentry/node';
@Catch()
export class AllExceptionsFilter extends BaseExceptionFilter {
private readonly logger = new Logger(AllExceptionsFilter.name);
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const request = ctx.getRequest<Request>();
const status =
exception instanceof HttpException
? exception.getStatus()
: 500;
// Logger
this.logger.error(
`[${request.method}] ${request.url} -> ${status}`,
(exception as any).stack,
);
// Sentry 전송
Sentry.captureException(exception);
// Nest 기본 처리 수행 (statusCode, message 응답 전송)
super.catch(exception, host);
}
}
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const { httpAdapter } = app.get(HttpAdapterHost);
app.useGlobalFilters(new AllExceptionsFilter(httpAdapter));
await app.listen(3000);
}
@Module({
providers: [
{
provide: APP_FILTER,
useClass: AllExceptionsFilter,
},
],
})
export class AppModule {}
요청: GET /users/999
예외: throw new NotFoundException('User not found')
클라이언트 응답:
{
"statusCode": 404,
"message": "User not found"
}
로그 (콘솔):
[ERROR] AllExceptionsFilter - [GET] /users/999 -> 404
Error: User not found
at ...
Sentry: 예외 상세 정보가 자동으로 전송