[TIL/Nest] 2025/04/30
NestJS에서 Filter는, 애플리케이션에서 발생하는 각종 예외를 Filtering 하기 위한 목적으로 사용합니다. 이 글은 Filter를 적용하는 과정에서 발생한 문제(AxiosError 관련 문제)에 대한 해결을 논의합니다.만약 Filter라는 개념 자체를 처음
0. Overview ✍️
NestJS에서 Filter는, 애플리케이션에서 발생하는 각종 예외를 Filtering 하기 위한 목적으로 사용합니다. 이 글은 Filter를 적용하는 과정에서 발생한 문제(AxiosError 관련 문제)에 대한 해결을 논의합니다.
만약 Filter라는 개념 자체를 처음 접하시는 분이라면, 이전 글을 참고하시길 바랍니다.
이전 글: https://velog.io/@minkwan/TILNest-20250421
1. http-exceptions.filter.ts ✍️
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus,
} from '@nestjs/common';
import { Request, Response } from 'express';
import { filterResponseParser } from './dto/filter-response-dto';
@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 instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const res: any = exception.getResponse();
const url: string = request.url;
const error: string = res.error;
const message: string = res?.message || 'http error 발생';
const timestamp: string = new Date().toISOString();
response.status(status).json(
filterResponseParser({
statusCode: status,
message,
data: null,
requestPath: url,
error,
timestamp,
}),
);
}
}가장 먼저 예외를 처리하는 catch 메서드를 정의합니다. 매개변수로 exception과 host를 받는 모습을 확인할 수 있습니다.
해당 메서드에서 exception의 타입은 NestJS에서 제공하는 HttpException입니다. 즉, catch 메서드에 들어오는 exception은 HttpException입니다. host의 타입은 ArgumentHost인데요, ArgumentHost 역시 NestJS에서 제공하는, 실행 컨텍스트에 접근할 수 있게 해주는 유틸리티입니다.
실행 컨텍스트에 접근한다는 말은, 다양한 실행 환경(HTTP, WebSockets, gRPC 등)에서 요청과 응답 객체에 접근한다는 말과 같습니다.
지금 상황의 경우 HTTP 요청임이 분명하지만, 규모가 커질수록 매번 어떠한 요청인지 확인하기 어렵기에, host 컨텍스트를 switchToHttp() 메서드로 분명하게 변환한 뒤, 해당 값을 ctx 변수에 할당합니다. 이후 해당 ctx로부터 response와 request를 추출합니다. 추가적으로 url이나 error, timestamp 등 필요로 하는 속성 역시 추출합니다.
마지막으로 해당 응답에 대한 status를 json 형식으로 포맷팅하여 반환하게 됩니다.
요컨대, http-exceptions.filter.ts는 HttpException이 발생한 경우, 그에 상응하는 에러를 미리 정해놓은 형식에 맞게 반환하는 Filter라고 할 수 있습니다.
2. catch-everything.filter.ts ✍️
다음은 catch-everything.filter.ts에 대한 로직입니다.
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus,
} from '@nestjs/common';
import { HttpAdapterHost } from '@nestjs/core';
import { filterResponseParser } from './dto/filter-response-dto';
@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 = filterResponseParser({
statusCode: httpStatus,
message: '서버 에러 발생',
data: null,
requestPath: httpAdapter.getRequestUrl(ctx.getRequest()),
});
httpAdapter.reply(ctx.getResponse(), responseBody, httpStatus);
}
}http-exceptions.filter.ts와 크게 다를 바 없습니다.
다만 파일 이름에 맞게 http exception이 아니라면, 모두 위 Filter에서 에러가 잡히게 됩니다.
3. Intentional Error ✍️
이제 의도적으로 에러를 발생시켜볼까 합니다. 예외 필터를 동작하게 하려면 예외를 발생시켜야겠죠.

토큰을 요청하는 url에 'ㄱ'을 추가했습니다. 잘못된 url 경로로 요청을 했다는 것은 HttpException이 발생했다는 것을 의미합니다. 따라서 사전에 작성해놓은 http-exceptions에서 에러가 잡혀야 합니다.

네트워크에서 에러 응답값을 확인해 보니, http-exceptions가 아니라 catch-everything 필터에서 에러가 잡혔다는 사실을 확인할 수 있습니다. HttpException을 의도적으로 발생시켰는데 catch-everything 필터에서 에러가 잡혔다는 것은, 제가 의도적으로 발생시킨 에러가 HttpException이 아니라는 사실을 의미합니다.
axios 통신 과정에서 발생한 에러는 HttpException이 아니라 AxiosError로 떨어집니다. 그렇다면 Service layer에서 모든 AxiosError를 HttpException으로 변환해 줘야 공용 필터인 HttpException으로 에러가 전달된다고 이해할 수 있습니다.
그런데 Http 관련 예외를 전역적으로 처리하기 위해서 Filter 로직을 따로 만든 것인데, 서비스 레이어에서 하나하나 AxiosError를 HttpException으로 변환해야 한다면 공용으로서의 의미를 상실하는 상황이 되겠죠.
그래서 "AxiosError를 HttpException으로 변환하는 공용 유틸 함수를 개발하는 게 좋지 않을까?"라는 생각을 하게 되었습니다.
4. convertAxiosErrorToHttpException() ✍️
import { HttpException, HttpStatus } from '@nestjs/common';
import { AxiosError } from 'axios';
export function convertAxiosErrorToHttpException(
error: unknown,
): HttpException {
// error가 AxiosError 타입인지 확인
if (
typeof error === 'object' &&
error !== null &&
'isAxiosError' in error &&
(error as any).isAxiosError
) {
const axiosError = error as AxiosError;
// Axios 응답 객체에서 HTTP 상태 코드를 가져오고, 없으면 502(Bad Gateway)로 설정
const status = axiosError.response?.status || HttpStatus.BAD_GATEWAY;
// Axios 응답 데이터에서 유의미한 필드들을 추출할 수 있도록 타입 단언
const responseData = axiosError.response?.data as
| { message?: string; error_description?: string; error?: string }
| undefined;
// 사용자에게 보여줄 메시지: message → error_description → 기본 Axios 메시지 순으로 우선순위 설정
const message =
responseData?.message ||
responseData?.error_description ||
axiosError.message ||
'Axios request failed';
// 에러 이름 또는 코드: error 필드가 없으면 기본값 'Bad Gateway' 사용
const errorMessage = responseData?.error || 'Bad Gateway';
// NestJS의 HttpException 인스턴스로 변환하여 반환 (일관된 포맷으로 필터와 호환)
return new HttpException(
{
statusCode: status,
message,
data: null,
error: errorMessage,
},
status,
);
}
// AxiosError가 아닌 일반 예외는 내부 서버 오류로 처리
return new HttpException(
{
statusCode: HttpStatus.INTERNAL_SERVER_ERROR,
message: 'Unexpected error occurred',
data: null,
error: 'InternalServerError',
},
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
에러가 AxiosError라면 일부 설정을 변경한 뒤, 최종적으로 HttpException 인스턴스로 변환하여 반환하는 코드입니다. 즉 서비스 레이어에서 위 함수를 실행했는데 AxiosError라면 해당 에러는 http-exceptions 필터로 넘어가게 됩니다.
만약 조건에 해당하지 않는다면(AxiosError가 아니라면), catch-everything filter로 예외가 넘어가게 될 것입니다.
5. Apply to Service Layer ✍️
Service Layer에 해당 함수를 적용했습니다.

아,,, 기가 막힌 모습을 확인할 수 있습니다.

6. Conclusion ✍️
Interceptors 개념을 적용할 수 있는 문제인지에 대한 고민이 남아있습니다. 관련해서 곧 Interceptors를 다룰 예정입니다.
More to read
Amazon VPC Architecture 이해하기
새로운 프로젝트를 기획하며, 개발에서 무엇을 가장 먼저 고민해야 하는지 다시 돌아보게 되었습니다.한때는 프론트엔드가 모든 설계의 출발점이라고 믿었습니다. 유저가 무엇을 보고, 어떤 흐름에서 머무르고 이탈하는지에 대한 이해 없이 서비스를 만든다는 건 불가능하다고 생각했기
'원사이트'프론트엔드 관점으로 알고리즘 이해하기
오랜만에 방법론에 관한 글을 쓰게 되었습니다. 최근 상황은 이렇습니다. SSAFY에서는 하루에 엄청난 양의 알고리즘 문제들을 과제로 수행하게 됩니다. 그 과정에서, '구현력'이 매우 떨어진다는 생각이 들었습니다. 완전히 어려운 문제라면 '아쉬움'이라는 감정조차 느끼지
SubnetVPC 설계의 시작: IP와 Subnet
반복되는 루틴 속에서 얻은 안정감을 발판 삼아, 이제는 기술적 스펙트럼을 넓히기 위한 개인 프로젝트에 착수하고자 합니다.이번 프로젝트의 목표는 단순한 포트폴리오 구축을 넘어, 실제 서비스 수준의 블로그 시스템 구현과 다국어 처리 적용 등 실무에 가까운 역량을 한 단계