본문으로 건너뛰기
개발 머꼬
개발 노트NestJS
hohyeon.dev25

쿼리 파라미터가 문자열이라 계산이 전부 틀어졌던 이유

  • #Engineering Note
  • #NestJS

문제 발생

페이지 번호를 받아 계산했는데 값이 이상했습니다.

@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 등입니다.

해결 방안

  1. 내장 파이프를 파라미터에 붙입니다.
@Get()
findAll(@Query("page", new DefaultValuePipe(1), ParseIntPipe) page: number) { ... }

page=abc는 이제 핸들러에 닿기 전에 400으로 끝납니다.

  1. 복합 입력은 DTO + ValidationPipe로 처리합니다. 필드가 두어 개를 넘으면 파라미터마다 파이프를 붙이는 것보다 DTO 하나가 읽기 쉽습니다.

  2. 적용 범위를 정합니다. 문서가 나열하는 세 가지 — 파라미터 단위(@Param('id', ParseIntPipe)), 핸들러 단위(@UsePipes()), 전역(app.useGlobalPipes() 또는 APP_PIPE provider)입니다. 전역인데 DI가 필요하면 provider 등록이어야 합니다.

  3. 커스텀 파이프는 변환만 하게 둡니다. 파이프 안에서 DB를 조회해 존재 여부까지 확인하기 시작하면 책임이 커집니다 — 그건 서비스나 가드의 일입니다.

  4. 오류 메시지를 다듬습니다. 기본 메시지는 개발자용입니다. 사용자에게 보여줄 화면이라면 예외 필터에서 형식을 통일합니다.

  5. 입력은 여기서만 믿습니다. 파이프를 통과했다는 것이 "형식이 맞다"는 뜻이지 "권한이 있다"는 뜻은 아닙니다 — 소유권 검사는 데이터 접근 지점에서 따로 합니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.