728x90
반응형
NestJS Exception Filters 정리
NestJS에는 애플리케이션 전역에서 처리되지 않은 모든 예외를 담당하는 예외 계층(Exception Layer) 이 내장되어 있습니다.
애플리케이션 코드에서 예외가 처리되지 않고 발생하면, 이 계층이 이를 포착하여 사용자 친화적인 HTTP 응답을 자동으로 반환합니다.
🌠 목차
- NestJS 요청 라이프사이클
- Exception Filters의 주요 목적
- Exception Filters가 에러를 잡을 수 있는 이유
- 예외 처리 방법들
- Exception Filters를 활용한 커스텀
- Filters vs Pipes
- Interceptor 개념 정리
- Guard 개념 정리
- Pipes와 Query / Body 처리 방식
NestJS 요청 라이프사이클

클라이언트 요청이 컨트롤러에 도달하기까지의 흐름은 다음과 같습니다.
- 클라이언트 요청
- 미들웨어 실행 (로깅, 인증 등)
- 가드 실행 (권한 체크: AuthGuard, AdminGuard 등)
- 인터셉터 시작
- 파이프 실행 (데이터 변환 / 유효성 검사)
- 컨트롤러 메서드 실행
- 서비스 로직 실행
- 인터셉터 종료
- 응답 반환
⚠️ 에러가 발생하면?
위 과정 중 어디에서든 예외가 발생하면
- Exception Filter가 에러를 캐치
- 적절한 에러 응답을 클라이언트에 반환
Exception Filters의 주요 목적
1️⃣ 에러 캐치
- 요청 라이프사이클 전역에서 발생한 예외를 처리
2️⃣ 에러 커스텀
- 에러 응답 포맷을 프로젝트 기준에 맞게 통일
- 메시지, timestamp, path 등 추가 가능
Exception Filters가 에러를 잡을 수 있는 이유
핵심은 @Catch() 데코레이터입니다.
export declare function Catch(
...exceptions: Array<Type<any> | Abstract<any>>
): ClassDecorator;
의미 정리
- ...exceptions
- 여러 개의 예외 타입을 Rest Parameter로 전달 가능
- Type
- 구체적인 클래스
- Abstract
- 추상 클래스 또는 인터페이스
- 반환값
- ClassDecorator
// Type 예시
class HttpException {}
// Abstract 예시
abstract class BaseException {}
interface IException {}
👉 즉, 특정 예외 타입만 골라서 필터링 가능합니다.
Node Modules 내부 동작 구조
BaseExceptionFilter
기본 예외 처리 로직을 담당하는 추상 필터 클래스입니다.
export declare class BaseExceptionFilter<T = any>
implements ExceptionFilter<T> {
catch(exception: T, host: ArgumentsHost): void;
}
- 기본 catch() 구현 제공
- 알 수 없는 에러 처리 로직 포함
BaseExceptionFilterContext
- 필터 인스턴스 생성 및 관리
- DI 컨테이너와 연동
ExceptionsHandler
예외 처리 시스템의 중심
- 발생한 예외를 적절한 필터로 라우팅
export declare class ExceptionsHandler extends BaseExceptionFilter {
next(exception: any, ctx: ArgumentsHost): void;
}
전체 흐름 요약
예외 발생
↓
Router / Middleware (최초 캐치)
↓
ExceptionsHandler
↓
BaseExceptionFilterContext
↓
BaseExceptionFilter or ExternalExceptionsFilter
예외 처리 방법들
기본 제공 방식
throw new HttpException('접근 금지!', HttpStatus.FORBIDDEN);
{
"statusCode": 403,
"message": "접근 금지!"
}
하지만 응답을 더 커스터마이징하고 싶다면?
➡️ Exception Filter를 사용합니다.
Exception Filters를 활용한 커스텀
@Catch(HttpException)
export class MyExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const response = host.switchToHttp().getResponse();
response.status(403).json({
statusCode: 403,
timestamp: new Date(),
message: '죄송합니다. 접근 권한이 없습니다.',
path: '/admin',
});
}
}
언제 쓰면 좋을까?
- 에러 로깅을 하고 싶을 때
- 에러 응답 포맷을 통일하고 싶을 때
- 특정 예외만 다르게 처리하고 싶을 때
여러 예외 처리
@Catch(HttpException, ValidationError)
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {}
}
@Catch()
export class CatchAllFilter implements ExceptionFilter {}
Exception Filters vs Pipes
Exception Filters
- 발생한 예외를 처리
- 클라이언트에 반환할 응답을 결정
Pipes
- 컨트롤러 진입 이전에 실행
- 데이터 변환 / 유효성 검사
❗ Pipe에서 발생한 예외도 결국 Exception Filter에서 처리됩니다.
Interceptor 정리
역할
- 메서드 실행 전/후 로직 추가 (AOP)
- 응답 변환
- 예외 변환
- 캐싱, 로깅, 타임아웃 처리
구조
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const now = Date.now();
console.log('Before...');
return next.handle().pipe(
tap(() => console.log(`After... ${Date.now() - now}ms`)),
);
}
}
Guard 정리
Guard란?
- 라우트 핸들러 실행 여부를 결정
- 권한, 인증, 역할 기반 제어
export interface CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean>;
}
Guard vs Middleware
구분GuardMiddleware
| 실행 시점 | 핸들러 직전 | 라우트 이전 |
| 역할 | 접근 허용 여부 결정 | 요청/응답 객체 수정 |
| Context | ExecutionContext 접근 가능 | 핸들러 정보 접근 불가 |
Pipes와 Query / Body 처리
Query String
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {}
{
"statusCode": 400,
"message": "Validation failed (numeric string is expected)",
"error": "Bad Request"
}
👉 잘못된 값이면 컨트롤러 진입 자체를 막음
Body 처리
- Pipe로 직접 검증 ❌
- class-validator 사용 ⭕️
export class CreateEventBody {
@IsUUID()
uuid?: string;
}
마무리
- Exception Filter는 에러 처리의 마지막 보루
- Pipe / Guard / Interceptor와 함께 사용할 때 진짜 위력이 나옵니다
👍 글이 도움이 됐다면 좋아요 한 번 눌러주세요
관심 받는 거 좋아합니다 😄
728x90
반응형
'📂 Backend Engineering' 카테고리의 다른 글
| (공식문서) NestJS Pipes와 queryString, Body가 데이터 처리하는 방법 (1) | 2025.02.18 |
|---|---|
| 테스트 코드 현실 적용기: Unit Testing 책을 통한 인사이트와 실무 경험 (0) | 2025.01.29 |
| [백엔드]기프티콘 도메인에 대한 이해 & 정리 & 회고 (0) | 2024.05.11 |
| [Cursor 페이지네이션] nestJS + Prisma - cursor 페이지네이션 방식 (성능 최적화, 직접 cursor 구현) (1) | 2024.03.31 |
| RDB와 NoSql 비교 및 장점 | AWS에서 제공하는 NoSql은? (0) | 2024.03.15 |
