본문으로 건너뛰기

버그 제보

이상한 점을 알려 주시면 고칠게요. 지금 보고 있는 페이지와 브라우저 정보가 함께 담겨요.

페이지:

Backend

NestJS Exception Filter와 Pipe

작성 2026. 10. 13.추가
목차7개 항목

요청 생명주기의 양 끝을 담당하는 두 장치다. Pipe는 컨트롤러에 들어가기 직전 데이터를 변환·검증하고, Exception Filter는 처리 중 던져진 예외를 잡아 클라이언트에게 보낼 응답 형태로 바꾼다.

Exception Filter

기본 예외

컨트롤러나 서비스에서 HttpException(또는 그 하위 클래스)을 던지면 Nest가 상태 코드와 메시지로 응답을 만든다.

throw new HttpException('api is broken', 401);
throw new HttpException({ success: false, message: 'api is broken' }, 401);
throw new UnauthorizedException('로그인이 필요합니다.'); // 401 내장 예외

BadRequestException(400), UnauthorizedException(401), ForbiddenException(403), NotFoundException(404), ConflictException(409) 같은 내장 예외 클래스를 쓰면 상태 코드를 외울 필요가 없다.

커스텀 필터로 응답 형식 통일하기

매번 응답 형식을 맞추는 대신, 모든 HttpException을 한곳에서 가공한다.

// common/exceptions/http-exception.filter.ts
import { ArgumentsHost, Catch, ExceptionFilter, 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();
    const error = exception.getResponse() as
      | string
      | { error: string; statusCode: number; message: string | string[] };
 
    // 직접 던진 문자열 에러는 { error }로 감싸고, 내장 에러(ValidationPipe 등)의 객체는 펼친다
    const body = typeof error === 'string' ? { error } : error;
    response.status(status).json({
      success: false,
      timestamp: new Date().toISOString(),
      path: request.url,
      ...body,
    });
  }
}

적용 범위

범위적용 방법
메서드핸들러에 @UseFilters(HttpExceptionFilter)
컨트롤러클래스에 @UseFilters(HttpExceptionFilter)
전역main.ts에서 app.useGlobalFilters(new HttpExceptionFilter())

예외가 나면 라우트 → 컨트롤러 → 전역 필터 순으로 가장 가까운 필터가 처리한다.

Pipe

파이프는 클라이언트 요청 데이터를 변환(transformation)하거나 검증(validation)해서 핸들러가 원하는 형태의 값을 받게 해 준다. 이름은 "앞 단계의 출력이 다음 단계의 입력이 되는" Pipes and Filters 패턴에서 왔다.

변환 + 검증: ParseIntPipe

URL 파라미터는 항상 문자열로 들어온다.

@Get(':id')
getOneCat(@Param('id', ParseIntPipe) id: number) {
  console.log(typeof id); // number
}
  • /cats/12 → id === 12 (숫자로 변환)
  • /cats/abc → 핸들러 실행 전에 400 에러: Validation failed (numeric string is expected)

여러 파이프를 나열하면 순서대로 실행된다: @Param('id', ParseIntPipe, PositiveIntPipe). 이 밖에 ParseBoolPipe, ParseUUIDPipe, DefaultValuePipe 등이 내장되어 있다.

DTO 바디 전체는 보통 전역 ValidationPipe로 검증한다. 설정과 옵션은 DTO와 Validation에 있다.

핵심 정리

  • 예외는 HttpException 계열로 던지고, 응답 형식은 @Catch 필터 하나로 통일한다.
  • 파이프는 핸들러 직전에 값 변환·검증을 담당하며, 실패하면 핸들러가 아예 실행되지 않는다.

댓글

GitHub 계정으로 댓글을 남길 수 있어요.