쿼리 파라미터가 문자열이라 계산이 전부 틀어졌던 이유
문제 발생
페이지 번호를 받아 계산했는데 값이 이상했습니다.
@Get()
findAll(@Query("page") page: number) {
const offset = (page - 1) * 20; // page 가 "2" 문자열이면? → NaN 은 아니지만
return this.service.list(offset);
}타입은 number라고 적혀 있는데 실제로는 문자열이 들어왔고, "2" - 1은 우연히 동작했지만 page=abc에서는 NaN이 그대로 DB 쿼리까지 내려갔습니다.
원인 분석
TypeScript 타입은 런타임에 아무것도 하지 않습니다. HTTP 쿼리는 문자열이고, 선언한 타입은 컴파일 시점에 지워집니다.
NestJS는 이 경계를 위한 도구를 제공합니다. 문서는 파이프의 두 가지 용도를 **변환(입력 데이터를 원하는 형태로, 예: 문자열을 정수로)**과 **검증(입력을 평가해 그대로 통과시키거나 예외를 던짐)**으로 정리합니다.
실행 시점도 명확합니다 — Nest는 메서드가 호출되기 직전에 파이프를 끼워 넣고, 파이프는 그 메서드로 향하는 인자를 받아 처리합니다. 그리고 중요한 성질 하나 — 파이프가 예외를 던지면 예외 계층이 처리하며 컨트롤러 메서드는 실행되지 않습니다. 잘못된 입력이 도메인 로직에 도달하지 못한다는 뜻입니다.
내장 파이프도 이미 있습니다 — ParseIntPipe, ParseUUIDPipe, ParseBoolPipe, ParseArrayPipe, ParseEnumPipe, DefaultValuePipe, ValidationPipe 등입니다.
해결 방안
- 내장 파이프를 파라미터에 붙입니다.
@Get()
findAll(@Query("page", new DefaultValuePipe(1), ParseIntPipe) page: number) { ... }page=abc는 이제 핸들러에 닿기 전에 400으로 끝납니다.
-
복합 입력은 DTO +
ValidationPipe로 처리합니다. 필드가 두어 개를 넘으면 파라미터마다 파이프를 붙이는 것보다 DTO 하나가 읽기 쉽습니다. -
적용 범위를 정합니다. 문서가 나열하는 세 가지 — 파라미터 단위(
@Param('id', ParseIntPipe)), 핸들러 단위(@UsePipes()), 전역(app.useGlobalPipes()또는APP_PIPEprovider)입니다. 전역인데 DI가 필요하면 provider 등록이어야 합니다. -
커스텀 파이프는 변환만 하게 둡니다. 파이프 안에서 DB를 조회해 존재 여부까지 확인하기 시작하면 책임이 커집니다 — 그건 서비스나 가드의 일입니다.
-
오류 메시지를 다듬습니다. 기본 메시지는 개발자용입니다. 사용자에게 보여줄 화면이라면 예외 필터에서 형식을 통일합니다.
-
입력은 여기서만 믿습니다. 파이프를 통과했다는 것이 "형식이 맞다"는 뜻이지 "권한이 있다"는 뜻은 아닙니다 — 소유권 검사는 데이터 접근 지점에서 따로 합니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.