DTO and Response Class Guide
Use this skill when creating or modifying DTO files in the dto/ folder.
Input DTO (Request)
import { IsDateString, IsEnum, IsNotEmpty, IsOptional, IsString, Length } from 'class-validator'; import { IsE164 } from 'src/app.utils';
import { EntityEnum } from '../entity.enums';
export class CreateEntityDto { @IsNotEmpty() @IsString() requiredField!: string;
@IsOptional() @IsString() @Length(1, 30) optionalField?: string;
@IsOptional() @IsDateString() dateField?: string;
@IsOptional() @IsEnum(EntityEnum) enumField?: EntityEnum; }
Input DTO Rules
Use class-validator decorators
-
@IsNotEmpty() : Required fields
-
@IsOptional() : Optional fields
-
@IsString() , @IsNumber() , @IsBoolean() : Type validation
-
@IsEnum() : Enum value validation
-
@Length() : String length limits
-
@IsDateString() : ISO 8601 date strings
Field Declaration
-
Required fields: ! (definite assignment assertion)
-
Optional fields: ? (optional)
Custom Decorators
-
Phone numbers: @IsE164() (E.164 format validation)
-
Define additional decorators in app.utils.ts as needed
Naming Conventions
-
Find: Find{Entity}Dto
-
Create: Create{Entity}Dto
-
Update: Update{Entity}Dto
-
Other: {Action}{Entity}Dto
Output Response Class
import { Expose } from 'class-transformer';
import { EntityEnum } from '../entity.enums';
export class FindEntityRes { @Expose() id!: string;
@Expose() name!: string;
@Expose() enumField!: EntityEnum;
@Expose() createdAt!: Date; }
Output Response Rules
@Expose() decorator is required
-
Use @Expose() on all fields to expose
-
Works with excludeExtraneousValues: true option
Naming Conventions
-
Find: Find{Entity}Res
-
Create: Create{Entity}Res
-
Update: Update{Entity}Res
-
Other: {Action}{Entity}Res
Entity → Response Transformation
import { mapTo } from '../app.utils';
// Usage in controller return mapTo(FindEntityRes, entity);
DTO/Response Same File Pattern
Define in same file when DTO and Response are closely related (e.g., update API):
// update-entity.dto.ts import { Expose } from 'class-transformer'; import { IsOptional, IsString, Length } from 'class-validator';
export class UpdateEntityDto { @IsOptional() @IsString() @Length(0, 30) name?: string; }
export class UpdateEntityRes { @Expose() id!: string;
@Expose() name!: string; }
mapTo Utility
Transformation function defined in app.utils.ts :
import { type ClassConstructor, plainToInstance } from 'class-transformer';
export function mapTo<T, I>(type: ClassConstructor<T>, instance: I): T { return plainToInstance(type, instance, { excludeExtraneousValues: true, }); }
Custom Validator Pattern
import { applyDecorators } from '@nestjs/common'; import { Matches } from 'class-validator';
export function IsE164(): PropertyDecorator { return applyDecorators( Matches(/^+[1-9]\d{1,14}$/, { message: '전화번호는 E.164을 준수해야 합니다.', }), ); }
File Structure
src/{module}/dto/ ├── create-{entity}.dto.ts ├── update-{entity}.dto.ts ├── find-{entity}.dto.ts └── index.ts # barrel export
index.ts (Barrel Export)
export * from './create-entity.dto'; export * from './update-entity.dto'; export * from './find-entity.dto';
ValidationPipe Configuration
Global pipe setup in main.ts :
app.useGlobalPipes( new ValidationPipe({ whitelist: true, // Remove undefined fields forbidNonWhitelisted: true, // Error on undefined fields transform: true, // Auto type transformation }), );