낙서장이자 오답 노트이자 컨닝 페이퍼
패스트캠퍼스 환급챌린지 23일차 : 코드팩토리의 백엔드 아카데미 : 한 번에 끝내는 NestJS 패키지 - 기초부터 MSA까지 강의 후기 본문
패스트캠퍼스 환급챌린지 23일차 : 코드팩토리의 백엔드 아카데미 : 한 번에 끝내는 NestJS 패키지 - 기초부터 MSA까지 강의 후기
NangIn 2025. 4. 23. 21:03본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성하였습니다.
강의 내용 정리
기본 Pipe 사용해보기
파이프의 적용 우선순위
- Global Pipe
- → main.ts에서 app.useGlobalPipes()로 등록 → 전체 라우트에 적용
- Controller Pipe → 잘 안씀
- → @UsePipes()를 컨트롤러 상단에 작성 → 해당 컨트롤러의 모든 핸들러에 적용
- Route Pipe → 잘 안씀
- → @UsePipes()를 메서드 단위에 작성 → 해당 라우트에만 적용
- Parameter Pipe → 가장 흔히 사용됨
- → 파라미터 단위로 변환·검증 수행
파라미터 Pipe 적용 이유
- 서버 에러 예방:
- 클라이언트가 숫자 대신 문자열 등 이상한 값을 보낼 경우, 서버에서 예외 발생
- → ParseIntPipe 등으로 유효성 검사하면 BadRequestException으로 방어 가능
- 실제 요청 예시:
- /movies/1 → 정상
- /movies/test → 서버 에러 발생 (예: NaN 에러)
기본 제공 파이프 실습
| 파이프명 | 기능 | 사용 예 |
| ParseIntPipe | 문자열 → 정수 변환, 실패 시 예외 발생 | @Param('id', ParseIntPipe) |
| ParseFloatPipe | 문자열 → 실수 변환 | @Param('id', ParseFloatPipe) |
| ParseBoolPipe | 문자열 → boolean으로 변환 (true, false) | @Param('flag', ParseBoolPipe) |
| ParseArrayPipe | 문자열을 배열로 해석 (a,b,c → ['a','b','c']) | @Query('ids', new ParseArrayPipe({ items: Number })) |
| ParseUUIDPipe | UUID 형식인지 검증 | @Param('uuid', ParseUUIDPipe) |
| ParseEnumPipe | enum 값인지 검증 | @Param('type', new ParseEnumPipe(MyEnum)) |
| DefaultValuePipe | 값이 없을 경우 기본값 지정 | @Query('page', new DefaultValuePipe(1)) |
에러 메시지 커스터마이징 (ParseIntPipe 예시)
@Param('id', new ParseIntPipe({
exceptionFactory: () => new BadRequestException('숫자를 입력해주세요.')
}))
- exceptionFactory: 사용자 정의 에러 메시지 처리 함수
- 기본 예외 메시지보다 더 직관적으로 사용자에게 안내 가능
Pipe 변환 확인 실습 예시
@Get(':id')
getMovie(@Param('id', ParseIntPipe) id: number) {
console.log(typeof id); // 'number' 출력
}
- 파이프 미적용 시: typeof id === 'string'
- 파이프 적용 시: typeof id === 'number'
ParseEnumPipe 실습
enum CarBrand {
Lamborghini = '람보르기니',
Ferrari = '페라리',
}
@Get(':brand')
getBrand(@Param('brand', new ParseEnumPipe(CarBrand)) brand: CarBrand) {
// CarBrand enum 값만 허용됨
}
- 람보르기니, 페라리는 허용
- 포르쉐 등 다른 값 → BadRequestException
DefaultValuePipe 실습
@Get()
getList(@Query('page', new DefaultValuePipe(1)) page: number) {
// page가 없으면 기본값 1 사용
}
- ?page=2 → 2 전달
- ?page= 또는 쿼리 없음 → 1 사용
파일 파이프는 사용 안 함
- ParseFilePipe는 사용하지 않음
- → multer 등의 라이브러리를 활용하여 파일 업로드 처리 예정
프로젝트 Pipe 적용하기
누락된 전제 조건: 인터셉터 누락 확인
- 클래스 밸리데이터를 사용하려면 ValidationPipe가 필요
- 그런데 ValidationPipe는 TransformInterceptor가 함께 있어야 함
- → DTO 변환을 보장함
- 해결:
- GenreController, DirectorController에도 @UseInterceptors(TransformInterceptor) 명시
- 기존 MovieController에는 이미 적용되어 있었음
모든 라우트에 ParseIntPipe 일괄 적용
- 목적:
- URL 파라미터(@Param)에 문자열이 아닌 숫자만 들어오도록 강제
- 숫자가 아닌 값이 들어올 경우 BadRequestException 발생
- 적용 대상
- MovieController
- @Get(':id'), @Patch(':id'), @Delete(':id') 등
- GenreController, DirectorController
- 각 ID 기반 라우트 (조회, 수정, 삭제 등)
- MovieController
- 적용 방식
-
@Get(':id') findOne(@Param('id', ParseIntPipe) id: number) { ... }- number 타입 명시 생략 가능하지만, 명시해주는 것이 명확성에 좋음
-
Custom Validation Pipe
왜 커스텀 파이프를 만들어야 하나요?
- 기본 제공되는 파이프들(ParseIntPipe, ValidationPipe, ParseBoolPipe 등)은 매우 유용함
- 하지만 특정 도메인 로직에 맞춘 맞춤형 유효성 검사가 필요할 때가 있음
- 예시: 영화 제목은 무조건 3글자 이상이어야 한다는 비즈니스 규칙 → 기본 파이프 X
파일 구조 및 구현 흐름
- 파일 생성
- movie/pipes/movie-title-validation.pipe.ts
- 클래스 선언 및 데코레이터
-
@Injectable() export class MovieTitleValidationPipe implements PipeTransform<string, string> { - @Injectable()로 Nest 컨테이너에 등록
- PipeTransform<InputType, OutputType> 인터페이스 구현
-
- 핵심 로직: transform() 메서드 구현
-
transform(value: string, metadat): string { if (value.length <= 2) { throw new BadRequestException('영화의 제목은 세 자 이상 작성해주세요.'); } return value; }
-
컨트롤러에서 사용하기
- 컨트롤러의 @Query() 또는 @Param()에 파이프 적용
-
@Get() findByTitle(@Query('title', MovieTitleValidationPipe) title: string) { ... }
-
- 위와 같이 적용하면 클라이언트가 쿼리로 title=다크라고 보내면 통과되지만, title=다는 거절됨
파이프 내부의 Metadata 활용법
- transform(value, metadata) → 2번째 인자 metadata로 다양한 정보 접근 가능
프로퍼티 설명 type body / query / param 구분 가능 metatype 해당 파라미터가 선언된 타입 (예: String, Number) data @Param('id', new CustomPipe('test'))의 'test'에 해당하는 키 값 - 조건 분기, 로깅, 리팩토링 등에서 유용
에러 메시지 커스터마이징 예시
- 사용자 친화적인 에러 메시지를 제공 가능
- 단순히 throw new BadRequestException(...) 활용
MovieTitleValidationPipe 버그 수정
발견된 버그 개요
- 커스텀 파이프인 MovieTitleValidationPipe가 쿼리 파라미터 title이 아예 없는 경우에도 무조건 실행됨
- 따라서 title을 입력하지 않고 요청하면 길이 검증 로직에서 예외 발생
- 기대 동작: title이 없는 경우엔 검증하지 않고 통과(Pass) 되어야 함
해결 방법
- transform() 함수 내부에 아래와 같은 입력 존재 여부 검사 추가:
-
transform(value: string): string { // ✅ 쿼리 값이 아예 없으면 그냥 통과 if (!value) return value; if (value.length <= 2) { throw new BadRequestException('영화의 제목은 세 자 이상 작성해주세요.'); } return value; }
-
- !value 조건으로 null, undefined, 빈 문자열 등 모든 falsy 값을 검사하여 early return 처리
적용 후 확인 사항
- title 파라미터 생략한 경우 → 정상 요청 처리됨
- title=Ri → "영화 제목은 세 자 이상" 에러 반환
- title=Inception → 필터링 정상 동작
학습 후기
NestJS의 파이프는 단순한 미들웨어로 생각하기 쉬웠지만, 이번 강의를 통해 요청 데이터를 가공하고 컨트롤러에 전달하기 전 핵심적인 전처리 도구라는 것을 제대로 이해하게 되었습니다. 특히 ParseIntPipe, ParseBoolPipe, ParseEnumPipe 등 기본적으로 제공되는 파이프들만으로도 웬만한 유효성 검증이나 타입 변환은 손쉽게 처리할 수 있었습니다. 그중에서도 파라미터 단위에 적용하는 파이프는 실무에서 가장 빈번하게 사용될 수밖에 없는 위치에 있다는 점이 인상 깊었습니다.
예를 들어 클라이언트가 /movies/test와 같은 잘못된 요청을 보냈을 때, 서버 내부에서 예기치 않은 에러가 발생해버리는 경우를 ParseIntPipe 하나로 손쉽게 방어할 수 있었습니다. 단순한 타입 변환 이상의 의미를 가지는 파이프는 NestJS에서 신뢰성 있는 API 서버를 구성하는 데 반드시 필요한 보호 장치라는 생각이 들었습니다. 또한 전역으로 적용하는 ValidationPipe와 함께 DTO를 검증하고, 파라미터에 Parse 계열의 파이프를 적용하는 구조는 코드의 명확성과 안정성을 동시에 확보할 수 있는 좋은 패턴이라고 느꼈습니다.
커스텀 파이프를 직접 만들어 본 것도 중요한 경험이었습니다. 특정 유즈케이스, 예컨대 영화 제목은 반드시 세 글자 이상이어야 한다는 도메인 규칙을 적용하는 데 기본 파이프만으로는 한계가 있었고, 이럴 때 커스텀 파이프를 만들어 적용하는 방식이 얼마나 유연하면서도 구조적인지 실감할 수 있었습니다. 처음엔 값이 아예 전달되지 않았을 때도 검증이 실행되는 버그가 있었지만, !value 체크를 통해 간단하게 해결할 수 있었고, 이런 예외 케이스를 직접 다뤄보면서 실무에서는 예상하지 못한 입력값까지 고려하는 것이 얼마나 중요한지도 함께 배울 수 있었습니다.
결론적으로 파이프는 단순한 필터링 수준을 넘어서, NestJS가 지향하는 구조적이고 명확한 서버 아키텍처를 구현하는 데 꼭 필요한 장치였습니다. 실무에서도 필수적으로 다뤄야 할 개념이며, 특히 커스텀 파이프를 통한 도메인 로직의 유효성 검증은 프론트엔드와의 계약을 지키고 사용자 경험을 보호하는 데 큰 역할을 할 수 있다고 느꼈습니다.
학습 인증샷



