서비스에서 던진 오류가 전부 500 Internal server error로 나간 이유
문제 발생
없는 글을 조회하면 404를 주고 싶었는데 클라이언트에는 항상 같은 응답이 갔습니다.
async findOne(id: number) {
const post = await this.repo.findById(id);
if (!post) throw new Error("게시글을 찾을 수 없습니다");
return post;
}{ "statusCode": 500, "message": "Internal server error" }프런트엔드는 "없음"과 "서버 장애"를 구분할 수 없었고, 서버 로그에는 정상 요청이 에러로 쌓였습니다.
원인 분석
내장 예외 필터의 기본 동작입니다. NestJS 문서는 예외가 처리되지 않았고 HttpException의 하위 클래스도 아닐 때 프레임워크가 {"statusCode": 500, "message": "Internal server error"}로 응답한다고 설명합니다.
즉 throw new Error("...")의 메시지는 응답에 실리지 않습니다. 이건 실수가 아니라 안전한 기본값입니다 — 내부 오류 메시지에는 스택, 쿼리, 파일 경로가 섞이기 쉬워서 그대로 내보내면 정보 노출이 됩니다.
문서는 반대로 statusCode와 message 속성을 가진 예외라면 그 값이 응답에 제대로 채워진다고도 적습니다. 그래서 의도한 상태 코드를 주려면 예외의 타입으로 말해야 합니다.
해결 방안
- 의미에 맞는 내장 예외를 던집니다.
HttpStatus를 직접 다루는 것보다 읽기 쉽습니다.
import { NotFoundException, ForbiddenException } from "@nestjs/common";
if (!post) throw new NotFoundException("게시글을 찾을 수 없습니다");
if (post.authorId !== userId) throw new ForbiddenException();- 직접 만들 때는
HttpException을 씁니다. 문서 설명대로 응답(문자열 또는 객체)과 상태 코드를 인자로 받고, 세 번째options로 원인(cause)을 넘길 수 있습니다.
throw new HttpException({ code: "POST_LOCKED" }, HttpStatus.CONFLICT);-
도메인 예외를 HTTP로 번역하는 지점을 하나 둡니다. 도메인 코드가
@nestjs/common을 몰라도 되게 하려면@Catch(DomainError)필터에서 매핑합니다. -
전역 필터에 DI가 필요하면
APP_FILTER입니다. 문서가 명시합니다 — 어떤 모듈 바깥에서useGlobalFilters()로 등록한 전역 필터는 모듈 컨텍스트 밖이라 의존성을 주입받을 수 없습니다. 로거를 주입하려면 모듈의 provider로 등록합니다. -
원본 오류는 남기고 응답은 좁힙니다. 사용자에게는 상태 코드와 짧은 메시지만, 로그에는 원인 전체를 남깁니다. 500을 그대로 두는 것도 때로는 정답입니다 — DB가 죽은 상황을 404로 바꾸면 장애가 보이지 않습니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.