Pipes

Pipes

ํŒŒ์ดํ”„๋Š” @Injectable() ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋กœ ์ฃผ์„์ด ๋‹ฌ๋ฆฐ ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค. ํŒŒ์ดํ”„๋Š” PipeTransform ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

ํŒŒ์ดํ”„์—๋Š” ๋‘ ๊ฐ€์ง€ ์ผ๋ฐ˜์ ์ธ ์‚ฌ์šฉ ์‚ฌ๋ก€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

  • transformation: ์ž…๋ ฅ ๋ฐ์ดํ„ฐ๋ฅผ ์›ํ•˜๋Š” ์ถœ๋ ฅ์œผ๋กœ ๋ณ€ํ™˜

  • validation: ์ž…๋ ฅ ๋ฐ์ดํ„ฐ๋ฅผ ํ‰๊ฐ€ํ•˜๊ณ  ์œ ํšจํ•˜๋‹ค๋ฉด ๋ณ€๊ฒฝ์—†์ด ๊ทธ๋Œ€๋กœ ์ „๋‹ฌํ•˜์‹ญ์‹œ์˜ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ๋ฐ์ดํ„ฐ๊ฐ€ ์ •ํ™•ํ•˜์ง€ ์•Š์„ ๋•Œ ์˜ˆ์™ธ๋ฅผ ๋˜์ง‘๋‹ˆ๋‹ค

๋‘ ๊ฒฝ์šฐ ๋ชจ๋‘ ํŒŒ์ดํ”„๋Š” ์ปจํŠธ๋กค๋Ÿฌ ๊ฒฝ๋กœ ์ฒ˜๋ฆฌ๊ธฐ์— ์˜ํ•ด ์ฒ˜๋ฆฌ๋˜๋Š” ์ธ์ˆ˜์—์„œ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. Nest๋Š” ๋ฉ”์†Œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋˜๊ธฐ ์ง์ „์— ํŒŒ์ดํ”„๋ฅผ ์‚ฝ์ž…ํ•˜๊ณ  ํŒŒ์ดํ”„๋Š” ๋ฉ”์†Œ๋“œ๋กœ ํ–ฅํ•˜๋Š” ์ธ์ˆ˜๋ฅผ ์ˆ˜์‹ ํ•ฉ๋‹ˆ๋‹ค. ์ด ์‹œ์ ์—์„œ ๋ชจ๋“  ๋ณ€ํ™˜ ๋˜๋Š” ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์ž‘์—…์ด ์ˆ˜ํ–‰ ๋œ ํ›„ (์ž ์žฌ์ ์œผ๋กœ) ๋ณ€ํ™˜ ๋œ ์ธ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฒฝ๋กœ ์ฒ˜๋ฆฌ๊ธฐ๊ฐ€ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.

info ํžŒํŠธ ํŒŒ์ดํ”„๋Š” ์˜ˆ์™ธ ์˜์—ญ ๋‚ด์—์„œ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ํŒŒ์ดํ”„๊ฐ€ ์˜ˆ์™ธ๋ฅผ ๋˜์งˆ ๋•Œ ์˜ˆ์™ธ ๊ณ„์ธต (์ „์—ญ ์˜ˆ์™ธ ํ•„ํ„ฐ ๋ฐ ํ˜„์žฌ ์ปจํ…์ŠคํŠธ์— ์ ์šฉ๋˜๋Š” ์˜ˆ์™ธ ํ•„ํ„ฐ์— ์˜ํ•ด ์ฒ˜๋ฆฌ๋จ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์œ„์™€ ๊ฐ™์ด ํŒŒ์ดํ”„์—์„œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์ดํ›„์— ์ปจํŠธ๋กค๋Ÿฌ ๋ฉ”์†Œ๋“œ๊ฐ€ ์‹คํ–‰๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

Built-in pipes

Nest์—๋Š” ์ฆ‰์‹œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ 3 ๊ฐ€์ง€ ํŒŒ์ดํ”„ (ValidationPipe), ParseIntPipe ๋ฐ ParseUUIDPipe๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ๋“ค์€ @nestjs/common ํŒจํ‚ค์ง€์—์„œ ๋‚ด๋ณด๋‚ด์ง‘๋‹ˆ๋‹ค. ๊ทธ๋“ค์ด ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€ ๋” ์ž˜ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•ด ์ฒ˜์Œ๋ถ€ํ„ฐ ๋‹ค์‹œ ๋งŒ๋“ค์–ด ๋ด…์‹œ๋‹ค.

ValidationPipe๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ์ฒ˜์Œ์—๋Š” ๋‹จ์ˆœํžˆ ์ž…๋ ฅ ๊ฐ’์„ ๊ฐ€์ ธ ์™€์„œ ๋™์ผ ๊ธฐ๋Šฅ์œผ๋กœ ๋™์ž‘ํ•˜๋Š” ๊ฐ™์€ ๊ฐ’์„ ์ฆ‰์‹œ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

@@filename(validation.pipe)
import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';

@Injectable()
export class ValidationPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    return value;
  }
}
@@switch
import { Injectable } from '@nestjs/common';

@Injectable()
export class ValidationPipe {
  transform(value, metadata) {
    return value;
  }
}

info ํžŒํŠธ PipeTransform <T, R>์€ T๊ฐ€ ์ž…๋ ฅ value์˜ ์œ ํ˜•์„ ๋‚˜ํƒ€๋‚ด๊ณ  R์€ transform()๋ฉ”์†Œ๋“œ์˜ ๋ฆฌํ„ด ์œ ํ˜•์„ ๋‚˜ํƒ€๋‚ด๋Š” ์ผ๋ฐ˜ ์ธํ„ฐํŽ˜์ด์Šค์ž…๋‹ˆ๋‹ค.

๋ชจ๋“  ํŒŒ์ดํ”„๋Š” transform()๋ฉ”์†Œ๋“œ๋ฅผ ์ œ๊ณตํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ฐฉ๋ฒ•์—๋Š” ๋‘ ๊ฐ€์ง€ ๋งค๊ฐœ ๋ณ€์ˆ˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

  • value

  • metadata

value๋Š” ํ˜„์žฌ ์ฒ˜๋ฆฌ ๋œ ์ธ์ˆ˜ (๋ผ์šฐํŠธ ์ฒ˜๋ฆฌ ๋ฐฉ๋ฒ•์— ์˜ํ•ด ์ˆ˜์‹ ๋˜๊ธฐ ์ „์—)์ด๋ฉฐ metadata๋Š” ๋ฉ”ํƒ€ ๋ฐ์ดํ„ฐ์ž…๋‹ˆ๋‹ค. ๋ฉ”ํƒ€ ๋ฐ์ดํ„ฐ ๊ฐœ์ฒด์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์†์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

export interface ArgumentMetadata {
  readonly type: 'body' | 'query' | 'param' | 'custom';
  readonly metatype?: Type<any>;
  readonly data?: string;
}

์ด๋Ÿฌํ•œ ์†์„ฑ์€ ํ˜„์žฌ ์ฒ˜๋ฆฌ ๋œ ์ธ์ˆ˜๋ฅผ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.

type

์ธ์ˆ˜๊ฐ€ body @Body(), query @Query(), param @Param() ๋˜๋Š” ์‚ฌ์šฉ์ž ์ •์˜ ๋งค๊ฐœ ๋ณ€์ˆ˜์ธ์ง€ ์—ฌ๋ถ€๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค (์—ฌ๊ธฐ์—์„œ ์ž์„ธํžˆ ์•Œ์•„๋ณด์‹ญ์‹œ์˜ค).

metatype

์ธ์ˆ˜์˜ ๋ฉ”ํƒ€ ํƒ€์ž… (์˜ˆ: String)์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ฐธ๊ณ : ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ ๋ฉ”์†Œ๋“œ ์„œ๋ช…์—์„œ ์œ ํ˜• ์„ ์–ธ์„ ์ƒ๋žตํ•˜๊ฑฐ๋‚˜ ๋ฐ”๋‹๋ผ JavaScript๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ๊ฐ’์€ undefined์ž…๋‹ˆ๋‹ค.

data

๋ฌธ์ž์—ด์ด ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ์— ์ „๋‹ฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค (์˜ˆ: @Body('string')). ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ๊ด„ํ˜ธ๋ฅผ ๋น„์›Œ๋‘๋ฉด ์ •์˜๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค(undefined).

warning ๊ฒฝ๊ณ  ๋ณ€ํ™˜ ์ค‘์— TypeScript ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ์‚ฌ๋ผ์ง‘๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋ฉ”์†Œ๋“œ ๋งค๊ฐœ ๋ณ€์ˆ˜์˜ ์œ ํ˜•์ด ํด๋ž˜์Šค ๋Œ€์‹  ์ธํ„ฐํŽ˜์ด์Šค๋กœ ์„ ์–ธ๋˜๋ฉด metatype ๊ฐ’์€ Object๊ฐ€๋ฉ๋‹ˆ๋‹ค.

Validation use case

CatsController์˜ create()๋ฉ”์†Œ๋“œ๋ฅผ ์ž์„ธํžˆ ์‚ดํŽด ๋ณด์ž.

@@filename()
@Post()
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}
@@switch
@Post()
async create(@Body() createCatDto) {
  this.catsService.create(createCatDto);
}

createCatDto ๋ณธ๋ฌธ ๋งค๊ฐœ ๋ณ€์ˆ˜์— ์ดˆ์ ์„ ๋งž์ถ”๊ฒ ์Šต๋‹ˆ๋‹ค. ์œ ํ˜•์€ CreateCatDto์ž…๋‹ˆ๋‹ค.

@@filename(create-cat.dto)
export class CreateCatDto {
  readonly name: string;
  readonly age: number;
  readonly breed: string;
}

create ๋ฉ”์†Œ๋“œ๋กœ ๋“ค์–ด์˜ค๋Š” ๋ชจ๋“  ์š”์ฒญ์— ์œ ํšจํ•œ ๋ณธ๋ฌธ์ด ํฌํ•จ๋˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ createCatDto ๊ฐ์ฒด์˜ ์„ธ ๋ฉค๋ฒ„๋ฅผ ํ™•์ธํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ ๋ฉ”์†Œ๋“œ ๋‚ด์—์„œ ์ด๋ฅผ ์ˆ˜ํ–‰ ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ SRP (๋‹จ์ผ ์ฑ…์ž„ ๊ทœ์น™)๋ฅผ ์œ„๋ฐ˜ํ•ฉ๋‹ˆ๋‹ค. ๋˜ ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์€ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๊ฑฐ๊ธฐ์—์„œ ์ž‘์—…์„ ์œ„์ž„ํ•˜๋Š” ๊ฒƒ์ด์ง€๋งŒ ๊ฐ ๋ฐฉ๋ฒ•์˜ ์‹œ์ž‘ ๋ถ€๋ถ„์—์„œ ์ด ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ธฐ๋ฅผ ์‚ฌ์šฉํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. ์œ ํšจ์„ฑ ๊ฒ€์ฆ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์€ ์–ด๋–ป์Šต๋‹ˆ๊นŒ? ์ด ๋ฐฉ๋ฒ•์€ ์ข‹์€ ์•„์ด๋””์–ด์ผ ์ˆ˜ ์žˆ์ง€๋งŒ ์ „์ฒด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜์žˆ๋Š” ์ผ๋ฐ˜ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ƒ์„ฑ ํ•  ์ˆ˜๋Š” ์—†์Šต๋‹ˆ๋‹ค (๋ฏธ๋“ค์›จ์–ด๋Š” ํ˜ธ์ถœ๋  ํ•ธ๋“ค๋Ÿฌ ๋ฐ ๊ทธ ๋งค๊ฐœ ๋ณ€์ˆ˜ ์ค‘ ํ•˜๋‚˜).

์ด๊ฒƒ์€ ํŒŒ์ดํ”„์— ์ด์ƒ์ ์ธ ๊ฒฝ์šฐ ์ธ ๊ฒƒ์œผ๋กœ ํŒ๋ช…๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๊ณ„์†ํ•ด์„œ ํ•˜๋‚˜ ๋งŒ๋“ค์–ด ๋ด…์‹œ๋‹ค.

Object schema validation

๊ฐœ์ฒด ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ์— ์‚ฌ์šฉํ•  ์ˆ˜์žˆ๋Š” ๋ช‡ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค. ํ•œ ๊ฐ€์ง€ ์ผ๋ฐ˜์ ์ธ ์ ‘๊ทผ ๋ฐฉ์‹์€ ์Šคํ‚ค๋งˆ ๊ธฐ๋ฐ˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. Joi ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ฝ์„ ์ˆ˜ ์žˆ๋Š” API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋งค์šฐ ๊ฐ„๋‹จํ•œ ๋ฐฉ์‹์œผ๋กœ ์Šคํ‚ค๋งˆ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Joi ๊ธฐ๋ฐ˜ ์Šคํ‚ค๋งˆ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํŒŒ์ดํ”„๋ฅผ ์‚ดํŽด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

ํ•„์š”ํ•œ ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•˜์—ฌ ์‹œ์ž‘ํ•˜์‹ญ์‹œ์˜ค.

$ npm install --save @hapi/joi
$ npm install --save-dev @types/hapi__joi

์•„๋ž˜ ์ฝ”๋“œ ์ƒ˜ํ”Œ์—์„œ ์Šคํ‚ค๋งˆ๋ฅผ ์ƒ์„ฑ์ž์ธ์ˆ˜๋กœ ์ทจํ•˜๋Š” ๊ฐ„๋‹จํ•œ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ Joi.validate()๋ฉ”์†Œ๋“œ๋ฅผ ์ ์šฉํ•˜๋ฉด ์ œ๊ณต๋œ ์Šคํ‚ค๋งˆ์— ๋Œ€ํ•ด ๋“ค์–ด์˜ค๋Š” ์ธ์ˆ˜์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•ฉ๋‹ˆ๋‹ค.

์•ž์—์„œ ์–ธ๊ธ‰ํ–ˆ๋“ฏ์ด ์œ ํšจ ํŒŒ์ดํ”„(Validation Pipe)๋Š” ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๊ณ  ๋ฐ˜ํ™˜ํ•˜๊ฑฐ๋‚˜ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.

๋‹ค์Œ ์„น์…˜์—์„œ๋Š” @UsePipes()๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฃผ์–ด์ง„ ์ปจํŠธ๋กค๋Ÿฌ ๋ฉ”์†Œ๋“œ์— ์ ์ ˆํ•œ ์Šคํ‚ค๋งˆ๋ฅผ ์ œ๊ณตํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์‚ดํŽด ๋ด…๋‹ˆ๋‹ค.

@@filename()
import * as Joi from '@hapi/joi';
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';

@Injectable()
export class JoiValidationPipe implements PipeTransform {
  constructor(private readonly schema: Object) {}

  transform(value: any, metadata: ArgumentMetadata) {
    const { error } = Joi.validate(value, this.schema);
    if (error) {
      throw new BadRequestException('Validation failed');
    }
    return value;
  }
}
@@switch
import * as Joi from '@hapi/joi';
import { Injectable, BadRequestException } from '@nestjs/common';

@Injectable()
export class JoiValidationPipe {
  constructor(schema) {
    this.schema = schema;
  }

  transform(value, metadata) {
    const { error } = Joi.validate(value, this.schema);
    if (error) {
      throw new BadRequestException('Validation failed');
    }
    return value;
  }
}

Binding pipes

ํŒŒ์ดํ”„๋ฅผ ๋ฐ”์ธ๋”ฉ (์ ์ ˆํ•œ ์ปจํŠธ๋กค๋Ÿฌ ๋‚˜ ํ•ธ๋“ค๋Ÿฌ์— ์—ฐ๊ฒฐ)ํ•˜๋Š” ๊ฒƒ์€ ๋งค์šฐ ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” @UsePipes()๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ํŒŒ์ดํ”„ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜์—ฌ Joi ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์Šคํ‚ค๋งˆ๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.

@@filename()
@Post()
@UsePipes(new JoiValidationPipe(createCatSchema))
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}
@@switch
@Post()
@Bind(Body())
@UsePipes(new JoiValidationPipe(createCatSchema))
async create(createCatDto) {
  this.catsService.create(createCatDto);
}

Class validator

warning ๊ฒฝ๊ณ  ์ด ์„น์…˜์˜ ๊ธฐ์ˆ ์—๋Š” TypeScript๊ฐ€ ํ•„์š”ํ•˜๋ฉฐ ์•ฑ์ด vanilla JavaScript๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ž‘์„ฑ๋œ ๊ฒฝ์šฐ์—๋Š” ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

๊ฒ€์ฆ ๊ธฐ์ˆ ์˜ ๋Œ€์ฒด ๊ตฌํ˜„์„ ์‚ดํŽด ๋ด…๋‹ˆ๋‹ค.

Nest๋Š” class-validator ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์™€ ์ž˜ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋†€๋ผ์šด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ๊ธฐ๋ฐ˜ ๊ฒ€์ฆ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ๊ธฐ๋ฐ˜ ๊ฒ€์ฆ์€ ์ฒ˜๋ฆฌ๋œ ์†์„ฑ์˜ ๋ฉ”ํƒ€ ํƒ€์ž…(metatype)์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ํŠนํžˆ Nest์˜ ํŒŒ์ดํ”„ ๊ธฐ๋Šฅ๊ณผ ๊ฒฐํ•ฉ๋  ๋•Œ ๋งค์šฐ ๊ฐ•๋ ฅํ•ฉ๋‹ˆ๋‹ค. ์‹œ์ž‘ํ•˜๊ธฐ ์ „์— ํ•„์š”ํ•œ ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.

$ npm i --save class-validator class-transformer

์ด๊ฒƒ๋“ค์ด ์„ค์น˜๋˜๋ฉด, CreateCatDto ํด๋ž˜์Šค์— ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ๋ช‡ ๊ฐœ๋ฅผ ์ถ”๊ฐ€ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@@filename(create-cat.dto)
import { IsString, IsInt } from 'class-validator';

export class CreateCatDto {
  @IsString()
  readonly name: string;

  @IsInt()
  readonly age: number;

  @IsString()
  readonly breed: string;
}

Info ํžŒํŠธ ํด๋ž˜์Šค ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ธฐ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ ์—ฌ๊ธฐ๋ฅผ ์ฐธ์กฐํ•˜์‹ญ์‹œ์˜ค.

์ด์ œ ValidationPipe ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@@filename(validation.pipe)
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';

@Injectable()
export class ValidationPipe implements PipeTransform<any> {
  async transform(value: any, { metatype }: ArgumentMetadata) {
    if (!metatype || !this.toValidate(metatype)) {
      return value;
    }
    const object = plainToClass(metatype, value);
    const errors = await validate(object);
    if (errors.length > 0) {
      throw new BadRequestException('Validation failed');
    }
    return value;
  }

  private toValidate(metatype: Function): boolean {
    const types: Function[] = [String, Boolean, Number, Array, Object];
    return !types.includes(metatype);
  }
}

warning ์•Œ๋ฆผ ์œ„์—์„œ, ์šฐ๋ฆฌ๋Š” class-transformer ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. class-validator ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์™€ ๋™์ผํ•œ ์ €์ž์— ์˜ํ•ด ๋งŒ๋“ค์–ด ์กŒ์œผ๋ฏ€๋กœ ๊ฒฐ๊ณผ์ ์œผ๋กœ ๋งค์šฐ ์ž˜ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

์ด ์ฝ”๋“œ๋ฅผ ์‚ดํŽด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ๋จผ์ €, transform()ํ•จ์ˆ˜๋Š” async์ž…๋‹ˆ๋‹ค. Nest๋Š” ๋™๊ธฐ์‹ ํŒŒ์ดํ”„์™€ ๋น„๋™๊ธฐ์‹ ํŒŒ์ดํ”„๋ฅผ ๋ชจ๋‘ ์ง€์›ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ํด๋ž˜์Šค ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ธฐ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์ค‘ ์ผ๋ถ€๊ฐ€ ๋น„๋™๊ธฐ ์ผ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ (์•ฝ์† ํ™œ์šฉ)์— ์ด๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ์œผ๋กœ ์šฐ๋ฆฌ๋Š” ๋ฉ”ํƒ€ ํƒ€์ž… ํ•„๋“œ (ArgumentMetadata์—์„œ ์ด ๋ฉค๋ฒ„๋ฅผ ์ถ”์ถœ)๋ฅผ metatype ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ถ”์ถœํ•˜๊ธฐ ์œ„ํ•ด ๊ตฌ์กฐ ์กฐ์ •์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์™„์ „ํ•œ ArgumentMetadata๋ฅผ ์–ป์€ ๋‹ค์Œ ๋ฉ”ํƒ€ ํƒ€์ž… ๋ณ€์ˆ˜๋ฅผ ํ• ๋‹นํ•˜๊ธฐ ์œ„ํ•œ ์ถ”๊ฐ€ ๋ช…๋ น๋ฌธ์„ ๊ฐ–๋Š” ๊ฒƒ์— ๋Œ€ํ•œ ๊ฐ„๋‹จํ•œ ์„ค๋ช…์ž…๋‹ˆ๋‹ค.

๋‹ค์Œ์œผ๋กœ ํ—ฌํผ ํ•จ์ˆ˜ toValidate()์— ์ฃผ๋ชฉํ•˜์‹ญ์‹œ์˜ค. ์ฒ˜๋ฆฌ์ค‘์ธ ํ˜„์žฌ ์ธ์ˆ˜๊ฐ€ ๊ธฐ๋ณธ JavaScript ์œ ํ˜•์ธ ๊ฒฝ์šฐ ์œ ํšจ์„ฑ ๊ฒ€์ฆ ๋‹จ๊ณ„๋ฅผ ์ƒ๋žตํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค (์Šคํ‚ค๋งˆ๋ฅผ ์ฒจ๋ถ€ ํ•  ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ์œ ํšจ์„ฑ ๊ฒ€์ฆ ๋‹จ๊ณ„๋ฅผ ํ†ตํ•ด ์Šคํ‚ค๋งˆ๋ฅผ ์‹คํ–‰ํ•  ์ด์œ ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค).

๋‹ค์Œ์œผ๋กœ ํด๋ž˜์Šค ๋ณ€ํ™˜๊ธฐ ํ•จ์ˆ˜ plainToClass()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ผ๋ฐ˜ JavaScript ์ธ์ˆ˜ ๊ฐ์ฒด๋ฅผ ์œ ํ˜•์ด ์ง€์ •๋œ ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ ์šฉ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋„คํŠธ์›Œํฌ ์š”์ฒญ์—์„œ ์—ญ ์ง๋ ฌํ™”๋˜๋Š” ์ˆ˜์‹  ๋ณธ๋ฌธ์—๋Š” ์œ ํ˜• ์ •๋ณด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ํด๋ž˜์Šค ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ธฐ๋Š” ์ด์ „์— DTO์— ๋Œ€ํ•ด ์ •์˜ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋ฏ€๋กœ ์ด ๋ณ€ํ™˜์„ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋งˆ์ง€๋ง‰์œผ๋กœ ์•ž์—์„œ ์–ธ๊ธ‰ํ–ˆ๋“ฏ์ด ์œ ํšจ ํŒŒ์ดํ”„(Validation Pipe)์ด๋ฏ€๋กœ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์€ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๊ฑฐ๋‚˜ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.

๋งˆ์ง€๋ง‰ ๋‹จ๊ณ„๋Š” ValidationPipe๋ฅผ ๋ฐ”์ธ๋”ฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์˜ˆ์™ธ ํ•„ํ„ฐ์™€ ์œ ์‚ฌํ•œ ํŒŒ์ดํ”„๋Š” ๋ถ„์„๋ฒ• ๋ฒ”์œ„, ์ปจํŠธ๋กค๋Ÿฌ ๋ฒ”์œ„ ๋˜๋Š” ์ „์—ญ ๋ฒ”์œ„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ํŒŒ์ดํ”„๋ฅผ ๋งค๊ฐœ ๋ณ€์ˆ˜ ๋ฒ”์œ„๋กœ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜ ์˜ˆ์—์„œ ํŒŒ์ดํ”„ ์ธ์Šคํ„ด์Šค๋ฅผ ๊ฒฝ๋กœ ๋งค๊ฐœ ๋ณ€์ˆ˜ @Body()๋ฐ์ฝ”๋ ˆ์ดํ„ฐ์— ์ง์ ‘ ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค.

@@filename(cats.controller)
@Post()
async create(
  @Body(new ValidationPipe()) createCatDto: CreateCatDto,
) {
  this.catsService.create(createCatDto);
}

๋งค๊ฐœ ๋ณ€์ˆ˜ ๋ฒ”์œ„ ํŒŒ์ดํ”„๋Š” ์œ ํšจ์„ฑ ๊ฒ€์ฆ ๋กœ์ง์ด ํ•˜๋‚˜์˜ ์ง€์ •๋œ ๋งค๊ฐœ ๋ณ€์ˆ˜์—๋งŒ ๊ด€๋ จ๋  ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

๋˜๋Š” ๋ฉ”์†Œ๋“œ ๋ ˆ๋ฒจ์—์„œ ํŒŒ์ดํ”„๋ฅผ ์„ค์ •ํ•˜๋ ค๋ฉด @UsePipes()๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค.

@@filename(cats.controller)
@Post()
@UsePipes(new ValidationPipe())
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

info ํžŒํŠธ @UsePipes()๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋Š”@nestjs/common ํŒจํ‚ค์ง€์—์„œ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.

์œ„์˜ ์˜ˆ์—์„œ ValidationPipe์ธ์Šคํ„ด์Šค๊ฐ€ ์ฆ‰์‹œ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋˜๋Š” ํด๋ž˜์Šค (์ธ์Šคํ„ด์Šค ์•„๋‹˜)๋ฅผ ์ „๋‹ฌํ•˜์—ฌ ์ธ์Šคํ„ด์Šคํ™”๋ฅผ ํ”„๋ ˆ์ž„ ์›Œํฌ์— ๋‚จ๊ฒจ๋‘๊ณ  ์ข…์† ์ฃผ์ž…์„ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.

@@filename(cats.controller)
@Post()
@UsePipes(ValidationPipe)
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

ValidationPipe๋Š” ๊ฐ€๋Šฅํ•œ ํ•œ ์ผ๋ฐ˜์ ์œผ๋กœ ๋งŒ๋“ค์–ด ์กŒ์œผ๋ฏ€๋กœ ์ „์ฒด ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์˜ ๋ชจ๋“  ๊ฒฝ๋กœ ์ฒ˜๋ฆฌ๊ธฐ์— ์ ์šฉ๋˜๋Š” global-scoped ํŒŒ์ดํ”„๋กœ ์„ค์ •ํ•ด ๋ด…์‹œ๋‹ค.

@@filename(main)
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(3000);
}
bootstrap();

warning ์•Œ๋ฆผ ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์•ฑ์˜ ๊ฒฝ์šฐ useGlobalPipes()๋ฉ”์†Œ๋“œ๋Š” ๊ฒŒ์ดํŠธ์›จ์ด ๋ฐ ๋งˆ์ดํฌ๋กœ ์„œ๋น„์Šค์— ๋Œ€ํ•œ ํŒŒ์ดํ”„๋ฅผ ์„ค์ •ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. "ํ‘œ์ค€"(ํ•˜์ด๋ธŒ๋ฆฌ๋“œ๊ฐ€ ์•„๋‹Œ) ๋งˆ์ดํฌ๋กœ ์„œ๋น„์Šค ์•ฑ์˜ ๊ฒฝ์šฐ useGlobalPipes()๋Š” ํŒŒ์ดํ”„๋ฅผ ์ „์ฒด์ ์œผ๋กœ ๋งˆ์šดํŠธํ•ฉ๋‹ˆ๋‹ค.

์ „์—ญ ํŒŒ์ดํ”„๋Š” ๋ชจ๋“  ์ปจํŠธ๋กค๋Ÿฌ์™€ ๋ชจ๋“  ๊ฒฝ๋กœ ์ฒ˜๋ฆฌ๊ธฐ์— ๋Œ€ํ•ด ์ „์ฒด ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์—์„œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์˜์กด์„ฑ ์ฃผ์ž…์˜ ๊ด€์ ์—์„œ, ๋ชจ๋“ˆ ์™ธ๋ถ€์—์„œ ๋“ฑ๋ก๋œ ์ „์—ญ ํŒŒ์ดํ”„ (์œ„์˜ ์˜ˆ์—์„œ์™€ ๊ฐ™์ด useGlobalPipes()๋กœ)๋Š” ๋ชจ๋“ˆ์˜ ์ปจํ…์ŠคํŠธ ๋ฐ–์—์„œ ์ˆ˜ํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์— ์˜์กด์„ฑ์„ ์ฃผ์ž…ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ ๊ตฌ์„ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ชจ๋“  ๋ชจ๋“ˆ์—์„œ ์ง์ ‘ ๊ธ€๋กœ๋ฒŒ ํŒŒ์ดํ”„๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@@filename(app.module)
import { Module } from '@nestjs/common';
import { APP_PIPE } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_PIPE,
      useClass: ValidationPipe,
    },
  ],
})
export class AppModule {}

info ํžŒํŠธ ํŒŒ์ดํ”„์— ๋Œ€ํ•œ ์˜์กด์„ฑ ์ฃผ์ž…์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด ์ด ์ ‘๊ทผ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•  ๋•Œ, ์ด ๊ตฌ์„ฑ์ด ์‚ฌ์šฉ๋˜๋Š” ๋ชจ๋“ˆ์— ๊ด€๊ณ„์—†์ด ํŒŒ์ดํ”„๋Š” ์‹ค์ œ๋กœ ๊ธ€๋กœ๋ฒŒ์ž…๋‹ˆ๋‹ค. ์–ด๋””์—์„œ ํ•ด์•ผ ํ•ฉ๋‹ˆ๊นŒ? ํŒŒ์ดํ”„ (์œ„์˜ ์˜ˆ์—์„œ๋Š” ValidationPipe)๊ฐ€ ์ •์˜๋œ ๋ชจ๋“ˆ์„ ์„ ํƒํ•˜์‹ญ์‹œ์˜ค. ๋˜ํ•œ ์ปค์Šคํ…€ ํ”„๋กœ ๋ฐ”์ด๋” ๋“ฑ๋ก์„ ๋‹ค๋ฃจ๋Š” ์œ ์ผํ•œ ๋ฐฉ๋ฒ•์€ useClass๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์•Œ์•„๋ณด์‹ญ์‹œ์˜ค.

Transformation use case

ํŒŒ์ดํ”„(Pipes)์˜ ์œ ์ผํ•œ ์‚ฌ์šฉ ์‚ฌ๋ก€๋Š” ์•„๋‹™๋‹ˆ๋‹ค. ์ด ์žฅ์˜ ์‹œ์ž‘ ๋ถ€๋ถ„์—์„œ ํŒŒ์ดํ”„๋Š” ์ž…๋ ฅ ๋ฐ์ดํ„ฐ๋ฅผ ์›ํ•˜๋Š” ์ถœ๋ ฅ์œผ๋กœ ๋ณ€ํ™˜ ํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ์–ธ๊ธ‰ํ–ˆ์Šต๋‹ˆ๋‹ค. transform ํ•จ์ˆ˜์—์„œ ๋ฐ˜ํ™˜๋œ ๊ฐ’์ด ์ธ์ˆ˜์˜ ์ด์ „ ๊ฐ’์„ ์™„์ „ํžˆ ๋ฌด์‹œํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ์–ธ์ œ ์œ ์šฉํ•œ๊ฐ€์š”? ๊ฒฝ์šฐ์— ๋”ฐ๋ผ ํด๋ผ์ด์–ธํŠธ์—์„œ ์ „๋‹ฌ๋œ ๋ฐ์ดํ„ฐ๋Š” ๊ฒฝ๋กœ ์ฒ˜๋ฆฌ๊ธฐ ๋ฉ”์„œ๋“œ์—์„œ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์ „์— ๋ฌธ์ž์—ด์„ ์ •์ˆ˜๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์ด ์•ฝ๊ฐ„์˜ ๋ณ€๊ฒฝ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ์ผ๋ถ€ ํ•„์ˆ˜ ๋ฐ์ดํ„ฐ ํ•„๋“œ๊ฐ€ ๋ˆ„๋ฝ๋˜์–ด ๊ธฐ๋ณธ๊ฐ’์„ ์ ์šฉํ•˜๋ ค๊ณ ํ•ฉ๋‹ˆ๋‹ค. ๋ณ€ํ™˜๊ธฐ ํŒŒ์ดํ”„๋Š” ํด๋ผ์ด์–ธํŠธ ์š”์ฒญ๊ณผ ์š”์ฒญ ํ•ธ๋“ค๋Ÿฌ ์‚ฌ์ด์— ์ฒ˜๋ฆฌ ๊ธฐ๋Šฅ์„ ์‚ฝ์ž…ํ•˜์—ฌ ์ด๋Ÿฌํ•œ ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ์€ ๋ฌธ์ž์—ด์„ ์ •์ˆ˜ ๊ฐ’์œผ๋กœ ๊ตฌ๋ฌธ ๋ถ„์„ํ•˜๋Š” ParseIntPipe์ž…๋‹ˆ๋‹ค.

@@filename(parse-int.pipe)
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';

@Injectable()
export class ParseIntPipe implements PipeTransform<string, number> {
  transform(value: string, metadata: ArgumentMetadata): number {
    const val = parseInt(value, 10);
    if (isNaN(val)) {
      throw new BadRequestException('Validation failed');
    }
    return val;
  }
}
@@switch
import { Injectable, BadRequestException} from '@nestjs/common';

@Injectable()
export class ParseIntPipe {
  transform(value, metadata) {
    const val = parseInt(value, 10);
    if (isNaN(val)) {
      throw new BadRequestException('Validation failed');
    }
    return val;
  }
}

์ด ํŒŒ์ดํ”„๋ฅผ ์•„๋ž˜ ๊ทธ๋ฆผ๊ณผ ๊ฐ™์ด ์„ ํƒํ•œ ๋งค๊ฐœ ๋ณ€์ˆ˜์— ๊ฐ„๋‹จํžˆ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@@filename()
@Get(':id')
async findOne(@Param('id', new ParseIntPipe()) id) {
  return await this.catsService.findOne(id);
}
@@switch
@Get(':id')
@Bind(Param('id', new ParseIntPipe()))
async findOne(id) {
  return await this.catsService.findOne(id);
}

์›ํ•˜๋Š” ๊ฒฝ์šฐ ๋ฌธ์ž์—ด์„ ๊ตฌ๋ฌธ ๋ถ„์„ํ•˜๊ณ  UUID์ธ์ง€ ํ™•์ธํ•˜๋Š” ParseUUIDPipe๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@@filename()
@Get(':id')
async findOne(@Param('id', new ParseUUIDPipe()) id) {
  return await this.catsService.findOne(id);
}
@@switch
@Get(':id')
@Bind(Param('id', new ParseUUIDPipe()))
async findOne(id) {
  return await this.catsService.findOne(id);
}

info ํžŒํŠธ ParseUUIDPipe()๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ๋ฒ„์ „ 3, 4 ๋˜๋Š” 5์—์„œ UUID๋ฅผ ๊ตฌ๋ฌธ ๋ถ„์„ํ•˜๋Š” ์ค‘์ž…๋‹ˆ๋‹ค. ํŠน์ • ๋ฒ„์ „์˜ UUID ๋งŒ ํ•„์š”ํ•œ ๊ฒฝ์šฐ ํŒŒ์ดํ”„ ์˜ต์…˜์—์„œ ๋ฒ„์ „์„ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์š”์ฒญ์ด ํ•ด๋‹น ํ•ธ๋“ค๋Ÿฌ์— ๋„๋‹ฌํ•˜๊ธฐ ์ „์— ParseIntPipe ๋˜๋Š” ParseUUIDPipe๊ฐ€ ์‹คํ–‰๋˜์–ด id ๋งค๊ฐœ ๋ณ€์ˆ˜์— ๋Œ€ํ•ด ํ•ญ์ƒ ์‚ฌ์šฉ๋œ ํŒŒ์ดํ”„์— ๋”ฐ๋ผ ์ •์ˆ˜ ๋˜๋Š” uuid๋ฅผ ์ˆ˜์‹ ํ•ฉ๋‹ˆ๋‹ค.

๋˜ ๋‹ค๋ฅธ ์œ ์šฉํ•œ ๊ฒฝ์šฐ๋Š” id๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๊ธฐ์กด ์‚ฌ์šฉ์ž ์—”ํ‹ฐํ‹ฐ๋ฅผ ์„ ํƒํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

@@filename()
@Get(':id')
findOne(@Param('id', UserByIdPipe) userEntity: UserEntity) {
  return userEntity;
}
@@switch
@Get(':id')
@Bind(Param('id', UserByIdPipe))
findOne(userEntity) {
  return userEntity;
}

์ด ํŒŒ์ดํ”„์˜ ๊ตฌํ˜„์€ ๋…์ž์—๊ฒŒ ๋งก๊ธฐ์ง€๋งŒ ๋‹ค๋ฅธ ๋ชจ๋“  ๋ณ€ํ™˜ ํŒŒ์ดํ”„์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์ž…๋ ฅ ๊ฐ’ (id)์„ ์ˆ˜์‹ ํ•˜๊ณ  ์ถœ๋ ฅ ๊ฐ’ (UserEntity ์˜ค๋ธŒ์ ํŠธ)๋ฅผ ๋ฆฌํ„ดํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋ณด์ผ๋Ÿฌ ํ”Œ๋ ˆ์ดํŠธ ์ฝ”๋“œ๋ฅผ ์ฒ˜๋ฆฌ๊ธฐ์—์„œ ์ผ๋ฐ˜ ํŒŒ์ดํ”„๋กœ ์ถ”์ƒํ™”ํ•˜์—ฌ ์ฝ”๋“œ๋ฅผ๋ณด๋‹ค ์„ ์–ธ์ ์ด๊ณ  ๊ฑด์กฐํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

The built-in ValidationPipe

๋‹คํ–‰์Šค๋Ÿฝ๊ฒŒ๋„, ValidationPipe์™€ ParseIntPipe๋Š” Nest out-of-the-the-box์—์„œ ์ œ๊ณต๋˜๋ฏ€๋กœ ์ด ํŒŒ์ดํ”„๋ฅผ ์ง์ ‘ ๋งŒ๋“ค ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค. ( ValidationPipe์—๋Š” class-validator๋ฐ class-transformerํŒจํ‚ค์ง€๊ฐ€ ๋ชจ๋‘ ์„ค์น˜๋˜์–ด ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.)

๋‚ด์žฅ๋œ ValidationPipe๋Š” ์ด ์žฅ์—์„œ ๊ตฌ์ถ•ํ•œ ์ƒ˜ํ”Œ๋ณด๋‹ค ๋” ๋งŽ์€ ์˜ต์…˜์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด ์˜ต์…˜์€ ํŒŒ์ดํ”„์˜ ๊ธฐ๋ณธ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ์„ค๋ช…ํ•˜๊ธฐ ์œ„ํ•ด ๊ธฐ๋ณธ์œผ๋กœ ์œ ์ง€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ ์˜ˆ์ œ๊ฐ€ ๋งŽ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌํ•œ ์˜ต์…˜ ์ค‘ ํ•˜๋‚˜๋Š” transform์ž…๋‹ˆ๋‹ค. ์—ญ ์ง๋ ฌํ™”๋œ ๋ฐ”๋”” ์˜ค๋ธŒ์ ํŠธ์— ๋Œ€ํ•œ ์ด์ „ ๋…ผ์˜๋Š” ๋ฐ”๋‹๋ผ JavaScript ์˜ค๋ธŒ์ ํŠธ (DTO ์œ ํ˜•์ด ์—†์Œ)์ž…๋‹ˆ๋‹ค. ์ง€๊ธˆ๊นŒ์ง€ ํŒŒ์ดํ”„๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŽ˜์ด๋กœ๋“œ์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ณผ์ •์—์„œ ์šฐ๋ฆฌ๋Š” class-transform์„ ์‚ฌ์šฉํ•˜์—ฌ ํ‰๋ฒ”ํ•œ ๊ฐ์ฒด๋ฅผ ์œ ํ˜•์ด ์ง€์ •๋œ ๊ฐ์ฒด๋กœ ์ž„์‹œ ๋ณ€ํ™˜ํ•˜์—ฌ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Œ์„ ์ƒ๊ธฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‚ด์žฅ๋œ ValidationPipe๋Š” ์„ ํƒ์ ์œผ๋กœ ์ด ๋ณ€ํ™˜ ๋œ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ตฌ์„ฑ ๊ฐ์ฒด๋ฅผ ํŒŒ์ดํ”„๋กœ ์ „๋‹ฌํ•˜์—ฌ ์ด ๋™์ž‘์„ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค. ์ด ์˜ต์…˜์˜ ๊ฒฝ์šฐ ์•„๋ž˜์™€ ๊ฐ™์ด ๊ฐ’์ด true์ธ transformํ•„๋“œ๊ฐ€์žˆ๋Š” ๊ตฌ์„ฑ ์˜ค๋ธŒ์ ํŠธ๋ฅผ ์ „๋‹ฌํ•˜์‹ญ์‹œ์˜ค.

@@filename(cats.controller)
@Post()
@UsePipes(new ValidationPipe({ transform: true }))
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

info ํžŒํŠธ ValidationPipe๋Š”@nestjs/common ํŒจํ‚ค์ง€์—์„œ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.

์ด ํŒŒ์ดํ”„๋Š” class-validator๋ฐ class-transformer๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋ฏ€๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ถ”๊ฐ€ ์˜ต์…˜์ด ๋งŽ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์œ„์˜ transform ์˜ต์…˜๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ํŒŒ์ดํ”„์— ์ „๋‹ฌ๋œ ๊ตฌ์„ฑ ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด ์ด๋Ÿฌํ•œ ์„ค์ •์„ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ์€ ๊ธฐ๋ณธ ์ œ๊ณต ์˜ต์…˜์ž…๋‹ˆ๋‹ค.

export interface ValidationPipeOptions extends ValidatorOptions {
  transform?: boolean;
  disableErrorMessages?: boolean;
  exceptionFactory?: (errors: ValidationError[]) => any;
}

์ด์™ธ์—๋„ ๋ชจ๋“  ํด๋ž˜์Šค ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ธฐ ์˜ต์…˜ (ValidatorOptions ์ธํ„ฐํŽ˜์ด์Šค์—์„œ ์ƒ ์†๋จ)์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Option

Type

Description

skipMissingProperties

boolean

true๋กœ ์„ค์ •ํ•˜๋ฉด ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ธฐ๋Š” ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๊ฐœ์ฒด์— ์—†๋Š” ๋ชจ๋“  ์†์„ฑ์˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ๊ฑด๋„ˆ ๋œ๋‹ˆ๋‹ค.

whitelist

boolean

true๋กœ ์„ค์ •ํ•˜๋ฉด ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ธฐ๋Š” ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ์†์„ฑ์˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ (๋ฐ˜ํ™˜ ๋œ) ๊ฐœ์ฒด๋ฅผ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.

forbidNonWhitelisted

boolean

true๋กœ ์„ค์ •ํ•˜๋ฉด ํ™”์ดํŠธ๋ฆฌ์ŠคํŠธ์— ์—†๋Š” ์†์„ฑ์„ ์ œ๊ฑฐํ•˜๋Š” ๋Œ€์‹  ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ธฐ๊ฐ€ ์˜ˆ์™ธ๋ฅผ throwํ•ฉ๋‹ˆ๋‹ค.

forbidUnknownValues

boolean

true๋กœ ์„ค์ •ํ•˜๋ฉด ์•Œ ์ˆ˜ ์—†๋Š” ๊ฐœ์ฒด์˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์‹œ๋„๊ฐ€ ์ฆ‰์‹œ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค.

disableErrorMessages

boolean

true๋กœ ์„ค์ •ํ•˜๋ฉด ์œ ํšจ์„ฑ ๊ฒ€์ฆ ์˜ค๋ฅ˜๊ฐ€ ํด๋ผ์ด์–ธํŠธ์— ๋ฆฌํ„ด๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

exceptionFactory

Function

๊ฒ€์ฆ ์—๋Ÿฌ์˜ ๋ฐฐ์—ด์„ ์ทจํ•ด, throw๋˜๋Š” ์˜ˆ์™ธ ๊ฐ์ฒด๋ฅผ ๋Œ๋ ค์ค๋‹ˆ๋‹ค.

groups

string[]

๊ฐœ์ฒด์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•˜๋Š” ๋™์•ˆ ์‚ฌ์šฉํ•  ๊ทธ๋ฃน์ž…๋‹ˆ๋‹ค.

dismissDefaultMessages

boolean

true๋กœ ์„ค์ •ํ•˜๋ฉด ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋Š” ๊ธฐ๋ณธ ๋ฉ”์‹œ์ง€๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ช…์‹œ ์ ์œผ๋กœ ์„ค์ •๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋Š” ํ•ญ์ƒ ์ •์˜๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

validationError.target

boolean

ValidationError์— ๋Œ€์ƒ์„ ๋…ธ์ถœํ•ด์•ผ ํ•˜๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.

validationError.value

boolean

ValidationError์— ๊ฒ€์ฆ๋œ ๊ฐ’์„ ํ‘œ์‹œํ•ด์•ผ ํ•˜๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.

info ์•Œ๋ฆผ class-validator ํŒจํ‚ค์ง€์— ๋Œ€ํ•œ ์ž์„ธํ•œ ์ •๋ณด๋Š” repository์—์„œ ์ฐพ์œผ์‹ญ์‹œ์˜ค.

Last updated

Was this helpful?