๐Ÿ“ก 04. DTO์™€ ๊ฒ€์ฆ โ€” ์“ฐ๋ ˆ๊ธฐ ๋ฐ์ดํ„ฐ๋Š” ์ž…๊ตฌ ์ปท (Garbage In, Error Out)

2026๋…„ 2์›” 26์ผ ์ˆ˜์ •๋จ

๐Ÿ“‹ ๊ฐœ์š”

์•ˆ์ „ํ•œ ๋ฐ์ดํ„ฐ ํ†ต์‹ ์˜ ํ•ต์‹ฌ์ธ DTO(Data Transfer Object)์™€ ํŒŒ์ดํ”„๋ฅผ ํ™œ์šฉํ•œ ๊ฐ•๋ ฅํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๊ธฐ๋ฒ•์„ ๋ฐฐ์›๋‹ˆ๋‹ค.

๐Ÿ“‹ ๋ชฉ์ฐจ


๐Ÿ“Œ ์ด ๋ฌธ์„œ๋ฅผ ์ฝ๊ธฐ ์ „์—

โฑ๏ธ ์˜ˆ์ƒ ์ฝ๊ธฐ ์‹œ๊ฐ„: 15๋ถ„

๐Ÿงณ ์ „์ œ ์ง€์‹ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

  • ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ(@) ๋ฌธ๋ฒ•์˜ ๊ธฐ๋ณธ ํ˜•ํƒœ๋ฅผ ์•ˆ๋‹ค.
  • 02์žฅ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ HTTP ๋ณธ๋ฌธ์„ ๊ฐ€์ ธ์˜ค๋Š” @Body() ์‚ฌ์šฉ๋ฒ•์„ ๋ณด์•˜๋‹ค.

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ํ๋ฆ„
๋ฐ์ดํ„ฐ ๊ฒ€์ฆ์˜ ํ•„์š”์„ฑ โ†’ class-validator ์„ค์ •๊ณผ ์‚ฌ์šฉ๋ฒ• โ†’ ํŒŒ์ดํ”„(Pipe)๋กœ ํ˜•๋ณ€ํ™˜ํ•˜๊ธฐ โ†’ ์ตœ์‹  ๋ฐฉ์‹์ธ Zod ์—ฐ๋™ โ†’ ์ž์ฃผ ๊ฒช๋Š” ์—๋Ÿฌ

๐ŸŽฏ ์ด ๋ฌธ์„œ๋ฅผ ๋‹ค ์ฝ์œผ๋ฉด ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ

  • ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ณด๋‚ธ ๋ฐ์ดํ„ฐ๊ฐ€ ์ˆซ์ž์ธ์ง€, ์ด๋ฉ”์ผ ํ˜•์‹์ธ์ง€ 100% ๋ณด์žฅํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๋ถˆํ•„์š”ํ•œ/์•…์˜์ ์ธ ํ•„๋“œ(admin: true ๋“ฑ)๋ฅผ ์„œ๋ฒ„ ์ž…๊ตฌ์—์„œ ์ž๋™์œผ๋กœ ๊ฑธ๋Ÿฌ๋‚ผ ์ˆ˜ ์žˆ๋‹ค.
  • Zod ์Šคํ‚ค๋งˆ๋ฅผ ์ด์šฉํ•ด DB ๋ชจ๋ธ๊ณผ ์™„๋ฒฝํ•˜๊ฒŒ ์ผ์น˜ํ•˜๋Š” DTO๋ฅผ ๋ฝ‘์•„๋‚ผ ์ˆ˜ ์žˆ๋‹ค.

๐Ÿค” ์™œ ์•Œ์•„์•ผ ํ•˜๋Š”๊ฐ€

ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ์•„๋ฌด๋ฆฌ ํผ(Form) ๊ฒ€์ฆ์„ ๋นก์„ธ๊ฒŒ ํ•ด๋†”๋„, ํ•ด์ปค๋‚˜ ์•…์„ฑ ๋ด‡์€ Postman ๊ฐ™์€ ํˆด๋กœ ์„œ๋ฒ„ API์— ์ง์ ‘ ์ด์ƒํ•œ ๋ฌธ์ž์—ด์„ ๊ฝ‚์•„ ๋„ฃ์–ด.
๋งŒ์•ฝ ๋‚˜์ด(age) ํ•„๋“œ์— {"age": "๋ฐฑ์‚ด"} ์ด๋ผ๋Š” ๋ฌธ์ž์—ด์ด ๊ทธ๋Œ€๋กœ DB์— ๋“ค์–ด๊ฐ€๋ ค๊ณ  ํ•œ๋‹ค๋ฉด? ์„œ๋ฒ„๊ฐ€ ๋ป—๊ฑฐ๋‚˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์˜ค์—ผ๋˜๊ฒ ์ง€?
DTO์™€ ํŒŒ์ดํ”„๋Š” ์ด๋Ÿฐ "์น˜๋ช…์ ์ธ ์“ฐ๋ ˆ๊ธฐ ๋ฐ์ดํ„ฐ"๋ฅผ ์ปจํŠธ๋กค๋Ÿฌ์— ๋„์ฐฉํ•˜๊ธฐ๋„ ์ „์— ๊ฐ€์ฐจ ์—†์ด ์ณ๋‚ด๋Š” ๋ฐฉ์–ด๋ง‰ ์ด์•ผ.


๐Ÿ—๏ธ ๋น„์œ ๋กœ ๋จผ์ € ์ดํ•ดํ•˜๊ธฐ

๐Ÿง’ 5์‚ด์—๊ฒŒ ์„ค๋ช…ํ•œ๋‹ค๋ฉด?

DTO (Data Transfer Object) ๋Š” ๊น๊นํ•œ ๊ณ ๊ธ‰ ํด๋Ÿฝ์˜ ์ž…์žฅ ์ œํ•œ ๊ทœ์น™ํ‘œ ์•ผ.
"๋‚˜์ด๋Š” ์ˆซ์ž์—ฌ์•ผ ํ•ด. ์ด๋ฆ„์€ ๋ฌด์กฐ๊ฑด 2๊ธ€์ž ์ด์ƒ์ด์–ด์•ผ ํ•ด. ์ด๋ฉ”์ผ ํ˜•์‹ ์•„๋‹ˆ๋ฉด ์ž…๊ตฌ ์ปท!"
ValidationPipe (์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ํŒŒ์ดํ”„) ๋Š” ๊ทธ ๊ทœ์น™ํ‘œ๋ฅผ ๋“ค๊ณ  ์„œ ์žˆ๋Š” ๋ฉ์น˜ ํฐ ๊ธฐ๋„(๋ฌธ์ง€๊ธฐ) ์•ผ.
์†๋‹˜(Request)์ด ์˜ค๋ฉด ์‹ ๋ถ„์ฆ(Body ๋ฐ์ดํ„ฐ)์„ ์ซ™ ํ›‘์–ด๋ณด๊ณ , ๊ทœ์น™ํ‘œ์— ํ•˜๋‚˜๋ผ๋„ ์–ด๊ธ‹๋‚˜๋ฉด "๋Œ์•„๊ฐ€๋ผ(400 Bad Request)" ํ•˜๊ณ  ๋ฌธ์ „๋ฐ•๋Œ€ํ•ด๋ฒ„๋ ค. ๋•๋ถ„์— ํด๋Ÿฝ ์•ˆ(์ปจํŠธ๋กค๋Ÿฌ์™€ ์„œ๋น„์Šค)์€ ํ•ญ์ƒ ๋ฌผ์ด ๊นจ๋—ํ•˜์ง€!


๐Ÿงฉ DTO์™€ class-validator โ€” ์ฒ ํ†ต ๋ณด์•ˆ ๋ฌธ์ง€๊ธฐ ๐ŸŸก

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • ์ „์—ญ ํŒŒ์ดํ”„ useGlobalPipes์˜ ํ•„์ˆ˜ ์„ค์ • 3๊ฐ€์ง€๋ฅผ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • @Is... ๊ณ„์—ด ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์ž์œ ๋กญ๊ฒŒ ์กฐํ•ฉํ•  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿค” ์ž ๊น, ๋จผ์ € ์ƒ๊ฐํ•ด๋ด
TypeScript๋Š” ์ปดํŒŒ์ผ ํƒ€์ž„์—๋งŒ ์ž‘๋™ํ•ด. ์‹คํ–‰ ์ค‘(๋Ÿฐํƒ€์ž„)์— ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ชฐ๋ž˜ JSON์„ ๋ณด๋‚ด๋ฉด TypeScript์˜ interface๋Š” ๊ทธ๊ฑธ ๋ง‰์•„์ค„ ์ˆ˜ ์žˆ์„๊นŒ?
(์ •๋‹ต: ๋ชป ๋ง‰์•„! ๊ทธ๋ž˜์„œ ๋Ÿฐํƒ€์ž„์— ์‚ด์•„๋‚จ๋Š” class์™€ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์“ฐ๋Š” ๊ฑฐ์•ผ.)

1๋‹จ๊ณ„: ์ „์—ญ ๊ธ€๋กœ๋ฒŒ ํŒŒ์ดํ”„(๊ธฐ๋„) ์„ธ์šฐ๊ธฐ

ํ”„๋กœ์ ํŠธ ์ „์ฒด์— ๋‹จ ํ•œ ๋ฒˆ๋งŒ ์„ค์ •ํ•˜๋ฉด ๋ผ. (main.ts)

import { ValidationPipe } from '@nestjs/common';
 
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  app.useGlobalPipes(new ValidationPipe({
    whitelist: true,            // 1. ๊ทœ์น™ํ‘œ์— ์—†๋Š” ์†์„ฑ์€ ์Šค์œฝ ๋ฌด์‹œ/์‚ญ์ œํ•ด๋ฒ„๋ฆผ (ํ•ต์‹ฌ ๋ณด์•ˆ!)
    forbidNonWhitelisted: true, // 2. ๊ทœ์น™ํ‘œ์— ์—†๋Š” ๊ฑธ ๋ณด๋‚ด๋ฉด ์•„์˜ˆ 400 ์—๋Ÿฌ๋ฅผ ๋ฑ‰์Œ (์„ ํƒ)
    transform: true,            // 3. ๋ฌธ์ž์—ด "1"์„ ์•Œ์•„์„œ ์ˆซ์ž 1๋กœ ๋ฐ”๊ฟ”์คŒ (๊ฐœ๊ฟ€ ๊ธฐ๋Šฅ)
  }));
  
  await app.listen(3000);
}

2๋‹จ๊ณ„: DTO ํด๋ž˜์Šค(๊ทœ์น™ํ‘œ) ๋งŒ๋“ค๊ธฐ

ํ•„์š”ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„ค์น˜ํ•˜๊ณ (npm i class-validator class-transformer),
๋งˆ์ž๋ง‰์œผ๋กœ DTO ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ๋•์ง€๋•์ง€ ๋ถ™์—ฌ์ฃผ๋ฉด ๋!

import { IsEmail, IsString, Length, IsOptional, Min } from 'class-validator';
 
export class CreateUserDto {
  @IsEmail({}, { message: '์ œ๋Œ€๋กœ ๋œ ์ด๋ฉ”์ผ์„ ์ ์–ด์ฃผ์„ธ์š”!' })
  email: string;
 
  @IsString()
  @Length(2, 20)
  name: string;
 
  @IsOptional()  // ๐Ÿ‘ˆ ์•ˆ ๋ณด๋‚ด๋„ ์ƒ๊ด€์—†๋‹ค๋Š” ๋œป!
  @Min(14, { message: '14์„ธ ์ด์ƒ๋งŒ ๊ฐ€์ž… ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.' })
  age?: number;
}

์ด์ œ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ @Body() dto: CreateUserDto ๋ผ๊ณ  ์ ๊ธฐ๋งŒ ํ•˜๋ฉด, ์ปจํŠธ๋กค๋Ÿฌ ๋‚ด๋ถ€ ์ฝ”๋“œ๋ถ€ํ„ฐ๋Š” dto.age๊ฐ€ 100% ์™„๋ฒฝํ•œ ์ˆซ์žํ˜•์ด๊ฑฐ๋‚˜, ์•„๋‹ˆ๋ฉด ์•„์˜ˆ ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๊ฒƒ ์„ ๋ง˜ ๋†“๊ณ  ํ™•์‹ ํ•  ์ˆ˜ ์žˆ์–ด.


๐Ÿงฉ ๋‚ด์žฅ ํŒŒ์ดํ”„์™€ ์œ ํ‹ธ๋ฆฌํ‹ฐ ๐ŸŸข

๋ณต์žกํ•œ ๊ฐ์ฒด ๋ฉ์–ด๋ฆฌ(@Body)๋Š” DTO์™€ class-validator๊ฐ€ ์ฒ˜๋ฆฌํ•ด ์ฃผ์ง€๋งŒ, ๋‹จ์ˆœํ•œ URL ๋ณ€์ˆ˜(@Param, @Query) ๊ฒ€์‚ฌ์—๋Š” ๋‚ด์žฅ ํŒŒ์ดํ”„(Built-in Pipe) ๊ฐ€ ์ตœ๊ณ ์•ผ.

๋Œ€ํ‘œ์ ์ธ ๋‚ด์žฅ ํŒŒ์ดํ”„ 4๊ฐ€์ง€

  1. ParseIntPipe: ๋ฌธ์ž์—ด์„ ๋ฌด์กฐ๊ฑด ์ •์ˆ˜๋กœ ๋ฐ”๊ฟˆ. ์‹คํŒจํ•˜๋ฉด 400 ์—๋Ÿฌ!
    @Get(':id')
    findOne(@Param('id', ParseIntPipe) id: number) {}
  2. ParseBoolPipe: "true", "false", "1", "0" ๋“ฑ์„ ๊น”๋”ํ•œ ๋ถˆ๋ฆฌ์–ธ ํƒ€์ž…์œผ๋กœ.
  3. ParseUUIDPipe: ๋ฌธ์ž์—ด์ด UUID ํฌ๋งท์ธ์ง€ ๊น๊นํ•˜๊ฒŒ ๊ฒ€์ฆ.
  4. DefaultValuePipe: ์ฟผ๋ฆฌ์ŠคํŠธ๋ง์ด ์•ˆ ๋„˜์–ด์™”์„ ๋•Œ ๊ธฐ๋ณธ๊ฐ’ ์„ธํŒ….
    @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number

Mapped Types (์ฝ”๋“œ ๋ณต๋ถ™ ๋ฐฉ์ง€๊ธฐ)

ํšŒ์›๊ฐ€์ž… ์šฉ DTO(CreateDto)๊ฐ€ ์žˆ๋Š”๋ฐ, ์ •๋ณด ์ˆ˜์ • ์šฉ DTO(UpdateDto)๋ฅผ ๋งŒ๋“ค๊ณ  ์‹ถ์„ ๋•Œ, ๋‚ด์šฉ๋ฌผ์„ ๋˜ ๋‹ค ๋ณต๋ถ™ํ•ด์•ผ ํ• ๊นŒ? NO!

import { PartialType } from '@nestjs/mapped-types';
 
// UpdateDto๋Š” CreateDto์™€ ๋˜‘๊ฐ™์ง€๋งŒ, ๋ชจ๋“  ํ•„๋“œ๊ฐ€ '์„ ํƒ(Optional)'์œผ๋กœ ๋ฐ”๋€œ!
export class UpdateUserDto extends PartialType(CreateUserDto) {}

์ด ์™ธ์—๋„ ํ•„๋“œ ์ผ๋ถ€๋งŒ ๊ฐ€์ ธ์˜ค๋Š” PickType, ์ผ๋ถ€๋งŒ ๋ฒ„๋ฆฌ๋Š” OmitType ๋“ฑ ๊ฐ•๋ ฅํ•œ ์ƒ์† ๊ธฐ๋Šฅ์ด ์ œ๊ณต๋ผ.


๐Ÿงช ๋”ฐ๋ผํ•ด๋ณด๊ธฐ: ์‹ค์ „ ํšŒ์›๊ฐ€์ž… DTO ๊นŽ์•„๋ณด๊ธฐ

์š”์ฆ˜ ๋Œ€์„ธ์ธ Zod ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ™œ์šฉํ•ด ๋ณผ ๊ฑฐ์•ผ.
NestJS์˜ ์ „ํ†ต์ ์ธ class-validator ๋Œ€์‹ , Drizzle ์Šคํ‚ค๋งˆ์™€ 100% ํ˜ธํ™˜๋˜๋Š” nestjs-zod๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํ˜„๋Œ€์ ์ธ ๋ฐฉ์‹์ด์ง€!

import { createZodDto } from 'nestjs-zod';
import { insertUserSchema } from '@repo/schema'; // Drizzle์—์„œ ์ž๋™ ์ƒ์„ฑํ•œ Zod ์Šคํ‚ค๋งˆ
 
// 1. Zod ์Šคํ‚ค๋งˆ์—์„œ ํ•„์š”ํ•œ ์†์„ฑ๋งŒ ์™ ๋นผ์˜จ๋‹ค (.pick)
const RegisterSchema = insertUserSchema.pick({
  email: true,
  password: true,
  name: true,
});
 
// 2. ์•Œ์•„์„œ ์™„๋ฒฝํ•œ DTO ํด๋ž˜์Šค๋กœ ๋‘”๊ฐ‘!
export class RegisterDto extends createZodDto(RegisterSchema) {}
 
// ----- ์ปจํŠธ๋กค๋Ÿฌ -----
@Post('register')
async register(@Body() dto: RegisterDto) {
  // dto.email, dto.password, dto.name ์ž๋™ ์™„์„ฑ ์ง€์›!
  // ๋ฌด์กฐ๊ฑด ๊ฒ€์ฆ๋œ ์•ˆ์ „ํ•œ ๋ฐ์ดํ„ฐ!
  return this.authService.register(dto);
}

โœ… Drizzle + Zod์˜ ์••๋„์ ์ธ ์žฅ์ :
DB ํ…Œ์ด๋ธ” ๊ตฌ์กฐ(Schema)๋ฅผ ๋ฐ”๊พธ๋ฉด? Zod ์Šคํ‚ค๋งˆ๋„ ์•Œ์•„์„œ ๋ฐ”๋€Œ๊ณ , DTO ํด๋ž˜์Šค๋„ ์•Œ์•„์„œ ๋ฐ”๋€Œ์–ด! ํด๋ผ์ด์–ธํŠธ๋ถ€ํ„ฐ DB๊นŒ์ง€ ๋‹จ์ผ ์ง„์‹ค ๊ณต๊ธ‰์›(SSOT) ์ด ์™„์„ฑ๋˜๋Š” ์งœ๋ฆฟํ•œ ๊ฒฝํ—˜์„ ํ•  ์ˆ˜ ์žˆ์–ด.


๐Ÿ’ผ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค์™€ ์‹ค๋ฌด ํŒ ๐ŸŸก

1. ์ „์—ญ whitelist: true๋Š” ๋ชฉ์ˆจ๊ณผ๋„ ๊ฐ™๋‹ค.

ํด๋ผ์ด์–ธํŠธ ๋ง˜๋Œ€๋กœ { "email": "a@a.com", "role": "admin" } ์ด๋ ‡๊ฒŒ role ํ•„๋“œ๋ฅผ ๊ฐ•์ œ๋กœ ์‘ค์…” ๋„ฃ์œผ๋ฉด ๊ถŒํ•œ ํƒˆ์ทจ ๋ฒ„๊ทธ๊ฐ€ ํ„ฐ์งˆ ์ˆ˜ ์žˆ์–ด.
whitelist: true๋ฅผ ์ผœ๋‘๋ฉด DTO์— ์ ํ˜€ ์žˆ์ง€ ์•Š์€ ํ•„๋“œ(role)๋Š” ๋ฌธ์ง€๊ธฐ๊ฐ€ ๋ฌด์ฐธํžˆ ์ฐ์–ด๋ฒ„๋ฆฌ๊ณ  ์•ˆ์ „ํ•œ ๋ฐ์ดํ„ฐ๋งŒ ํ†ต๊ณผ์‹œ์ผœ์ค˜.

2. ์ค‘์ฒฉ๋œ ๊ฐ์ฒด ๋ฐฐ์—ด ๊ฒ€์ฆ

์‚ฌ์šฉ์ž๊ฐ€ "์žฅ๋ฐ”๊ตฌ๋‹ˆ ๋ฐฐ์—ด"์„ ์ „์†กํ–ˆ๋‹ค๋ฉด, ๊ฐ ๋ฐฐ์—ด ์•„์ดํ…œ๋„ ์ผ์ผ์ด ๊ฒ€์ฆํ•ด์•ผ ํ•ด.
์ด๋•Œ๋Š” @ValidateNested()๊ฐ€ ํ•„์ˆ˜์•ผ.

import { ValidateNested, Type } from 'class-transformer';
 
class OrderItemDto { ... }
 
class CreateOrderDto {
  // 1. ์ด๊ฑฐ ๋ฐฐ์—ด์ธ์ง€ ๊ฒ€์‚ฌ
  @IsArray() 
  // 2. ๋‚ด๋ถ€๋ฅผ ์ƒ…์ƒ…์ด ๋’ค์ ธ์ค„๊ฒŒ!
  @ValidateNested({ each: true }) 
  // 3. ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋Š” ๋Ÿฐํƒ€์ž„์— ๋ฐฐ์—ด ์•ˆ์˜ ํƒ€์ž…์„ ๋ชจ๋ฅด๋‹ˆ๊นŒ Type์„ ๋‹ฌ์•„์ค˜์•ผ ํ•ด!
  @Type(() => OrderItemDto) 
  items: OrderItemDto[];
}

๐Ÿ’ฅ ์—๋Ÿฌ ํ•ด๊ฒฐ ์นดํƒˆ๋กœ๊ทธ

์—๋Ÿฌ ๋ฉ”์‹œ์ง€๊ฐ€ ๋œจ๋ฉด Ctrl+F๋กœ ๊ฒ€์ƒ‰ํ•ด๋ด.

โŒ Validation failed (numeric string is expected)

ํ˜„์ƒ:

{ "statusCode": 400, "message": "Validation failed (numeric string is expected)", "error": "Bad Request" }

์›์ธ: ์ปจํŠธ๋กค๋Ÿฌ ํŒŒ๋ผ๋ฏธํ„ฐ์—์„œ ParseIntPipe๋ฅผ ๋‹ฌ์•„๋†จ๋Š”๋ฐ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ˆซ์ž๊ฐ€ ์•„๋‹Œ ๊ธ€์ž(์˜ˆ: ?page=abc)๋ฅผ ๋ณด๋ƒˆ๊ธฐ ๋•Œ๋ฌธ.

ํ•ด๊ฒฐ์ฑ…:
๋‚ด ์ฝ”๋“œ๊ฐ€ ์ž˜๋ชป๋œ ๊ฒŒ ์•„๋‹ˆ์•ผ. ๋ฐฉ์–ด์— ์„ฑ๊ณตํ•œ ๊ฑฐ๋‹ˆ๊นŒ ํ”„๋ก ํŠธ๊ฐœ๋ฐœ์ž์—๊ฒŒ "์ˆซ์ž๋กœ ์ฃผ์„ธ์š”~" ๋ผ๊ณ  ๋งํ•˜๋ฉด ๋ผ.

โŒ property 'nestedObject' should not exist (whitelist ์—๋Ÿฌ)

์›์ธ:
์ „์—ญ ์„ค์ •์— whitelist: true, forbidNonWhitelisted: true๊ฐ€ ์ผœ์ ธ ์žˆ๋Š”๋ฐ, DTO ๊ทœ์น™์— ๋ช…์‹œ๋˜์ง€ ์•Š์€ ์ด์ƒํ•œ ์†์„ฑ์„ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ํŽ˜์ด๋กœ๋“œ์— ๊ปด์„œ ๋ณด๋ƒ„.

ํ•ด๊ฒฐ์ฑ…:
ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋งž๋‹ค๋ฉด DTO ํด๋ž˜์Šค์— ์†์„ฑ์„ ์ถ”๊ฐ€ํ•ด. (๋งŒ์•ฝ ๊ตณ์ด ๊ฒ€์‚ฌํ•  ํ•„์š” ์—†๋Š” ์ž์œ ํ˜•์‹ ๋ฐ์ดํ„ฐ๋ผ๋ฉด @IsOptional() ์ด๋ผ๋„ ๋‹ฌ์•„์ค˜์•ผ ํ•ด)


๐Ÿ—‚๏ธ ์น˜ํŠธ์‹œํŠธ โ€” ์‹ค๋ฌด ์š”์•ฝ ์นด๋“œ

์ƒํ™ฉ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ / ์„ค์ •์„ค๋ช…
๋ฌธ์ž์—ด์˜ ๊ธธ์ด๋ฅผ ์ œํ•œํ•˜๊ณ  ์‹ถ์–ด์š”@Length(2, 10)์ตœ์†Œ 2, ์ตœ๋Œ€ 10์ž
ํŠน์ • ๋ฌธ์ž์—ด ์…‹ ์ค‘ 1๊ฐœ์—ฌ์•ผ ํ•ด์š”@IsIn(['USER', 'ADMIN'])Enum ๋А๋‚Œ์˜ ๊ฒ€์ฆ
๋ฐฐ์—ด์ด ๋น„์–ด์žˆ์œผ๋ฉด ์•ˆ ๋ผ์š”@ArrayMinSize(1)length๊ฐ€ ์ตœ์†Œ 1 ์ด์ƒ
์„ ํƒ์  ๊ฐ’์ด๋ผ ์•ˆ ๋“ค์–ด์™€๋„ ๋ผ์š”@IsOptional()null, undefined ํŒจ์Šค
๋ณต์žกํ•œ ๊ฐ์ฒด/๋ฐฐ์—ด ๋‚ด๋ถ€๋ฅผ ๊ฒ€์‚ฌํ• ๋ž˜์š”@Type(() => Class)
@ValidateNested()
์ด๊ฒƒ๋“ค์€ ์„ธํŠธ๋กœ ๋‹ค๋…€์•ผ ์ž‘๋™ํ•จ

๐Ÿ“ ๋งˆ๋ฌด๋ฆฌ ํ€ด์ฆˆ

Q1. class-validator๋ฅผ ์ด์šฉํ•  ๋•Œ ์•„๋ž˜ ์„ค์ • ์ค‘, ํ•ด์ปค๊ฐ€ ์•…์˜์ ์ธ ์ž‰์—ฌ ํ•„๋“œ(isPaid: true ๋“ฑ)๋ฅผ JSON์— ์„ž์–ด ๋ณด๋‚ด๋Š” ๊ฒƒ์„ ๋ง‰๊ธฐ ์œ„ํ•ด ๋ฐ˜๋“œ์‹œ ์ผœ์•ผ ํ•˜๋Š” ์˜ต์…˜์€?

app.useGlobalPipes(new ValidationPipe({
  __________: true
}));
  • A) transform
  • B) whitelist
  • C) disableErrorMessages
  • D) enableCors

โœ… ์ •๋‹ต: B

๐Ÿ’ก ์„ค๋ช…: whitelist: true๋Š” ๋งˆ์น˜ ํ™”์ดํŠธ๋ฆฌ์ŠคํŠธ(ํ—ˆ๊ฐ€ ๋ช…๋‹จ) ๊ธฐ๋ฐ˜ ์ถœ์ž… ํ†ต์ œ์™€ ๊ฐ™์•„. DTO์— ์ด๋ฆ„์ด ์•ˆ ์ ํ˜€์žˆ๋Š” ๋ฐ์ดํ„ฐ ํ•„๋“œ๋Š” ์กฐ์šฉํžˆ ํ๊ธฐ์ฒ˜๋ถ„ ํ•ด๋ฒ„๋ ค!

Q2. ๋ฌธ์ž์—ด ํ˜•ํƒœ์˜ ์ฟผ๋ฆฌ์ŠคํŠธ๋ง ?limit=50 ์„ ์ˆซ์ž์ธ 50์œผ๋กœ ์•ˆ์ „ํ•˜๊ฒŒ ํ˜• ๋ณ€ํ™˜ํ•˜๋ฉด์„œ ๋ค์œผ๋กœ ๋ฌธ์ž์ธ์ง€ ์•„๋‹Œ์ง€ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊นŒ์ง€ ๊ณต์งœ๋กœ ํ•ด์ฃผ๋Š” ๋งˆ๋ฒ• ๊ฐ™์€ ๋‚ด์žฅ ํŒŒ์ดํ”„๋Š” ๋ฌด์—‡์ธ๊ฐ€?

โœ… ์ •๋‹ต: ParseIntPipe
๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: ์ •์ˆ˜(Int)๋กœ ๋ถ„์„(Parse) ํŒŒ์ดํ”„!

Q3. ๋””๋ฒ„๊น… ํ€ด์ฆˆ: ์•„๋ž˜ ์ฝ”๋“œ์—์„œ ๊ฐ์ฒด ๋ฐฐ์—ด ๊ฒ€์ฆ์ด ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š๋Š” ์›์ธ์„ ์ฐพ์•„๋ด.

import { IsArray, ValidateNested } from 'class-validator';
class TaskDto { @IsString() title: string; }
 
class CreateProjectDto {
  @IsArray()
  @ValidateNested({ each: true })
  tasks: TaskDto[];
}

ํ…Œ์ŠคํŠธ๋กœ tasks: [{ title: 12345 }] ๋ผ๊ณ  ์ˆซ์ž๋ฅผ ๋„ฃ์–ด์„œ ๋ณด๋ƒˆ๋Š”๋ฐ, ์—๋Ÿฌ๊ฐ€ ์•ˆ ๋‚˜๊ณ  ํ†ต๊ณผํ•ด๋ฒ„๋ฆฐ๋‹ค?!

โœ… ์ •๋‹ต: @Type(() => TaskDto) ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ๋นผ๋จน์—ˆ๊ธฐ ๋•Œ๋ฌธ!
**์„ค๋ช…:**TypeScript์˜ ํƒ€์ž… ์ •๋ณด๋Š” ์ปดํŒŒ์ผ ํ›„ JS๋กœ ๋ณ€ํ•˜๋ฉด ๊ฐ์ชฝ๊ฐ™์ด ์‚ฌ๋ผ์ ธ. ๋Ÿฐํƒ€์ž„์— ๋„๋Š” ํŒŒ์ดํ”„ ์ž…์žฅ์—์„  ์ € tasks ๋ฐฐ์—ด ์•ˆ์˜ ์›์†Œ๊ฐ€ TaskDto ๊ฐ์ฒด๊ฐ€ ๋งž๋Š”์ง€ ์•Œ ๊ธธ์ด ์—†์–ด. ๋ฌด์กฐ๊ฑด ๋ช…์‹œ์ ์œผ๋กœ @Type ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ๋‹ฌ์•„์ค˜์•ผ ์ค‘์ฒฉ ๊ฒ€์ฆ์ด ์ž‘๋™ํ•ด!


๐Ÿ”— ๋” ์•Œ์•„๋ณด๊ธฐ