--- title: Validate All Input with DTOs and Pipes impact: HIGH impactDescription: First line of defense against attacks tags: security, validation, dto, pipes --- ## Validate All Input with DTOs and Pipes Always validate incoming data using class-validator decorators on DTOs and the global ValidationPipe. Never trust user input. Validate all request bodies, query parameters, and route parameters before processing. **Incorrect (trust raw input without validation):** ```typescript // Trust raw input without validation @Controller('users') export class UsersController { @Post() create(@Body() body: any) { // body could contain anything - SQL injection, XSS, etc. return this.usersService.create(body); } @Get() findAll(@Query() query: any) { // query.limit could be "'; DROP TABLE users; --" return this.usersService.findAll(query.limit); } } // DTOs without validation decorators export class CreateUserDto { name: string; // No validation email: string; // Could be "not-an-email" age: number; // Could be "abc" or -999 } ``` **Correct (validated DTOs with global ValidationPipe):** ```typescript // Enable ValidationPipe globally in main.ts async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes( new ValidationPipe({ whitelist: true, // Strip unknown properties forbidNonWhitelisted: true, // Throw on unknown properties transform: true, // Auto-transform to DTO types transformOptions: { enableImplicitConversion: true, }, }), ); await app.listen(3000); } // Create well-validated DTOs import { IsString, IsEmail, IsInt, Min, Max, IsOptional, MinLength, MaxLength, Matches, IsNotEmpty, } from 'class-validator'; import { Transform, Type } from 'class-transformer'; export class CreateUserDto { @IsString() @IsNotEmpty() @MinLength(2) @MaxLength(100) @Transform(({ value }) => value?.trim()) name: string; @IsEmail() @Transform(({ value }) => value?.toLowerCase().trim()) email: string; @IsInt() @Min(0) @Max(150) age: number; @IsString() @MinLength(8) @MaxLength(100) @Matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/, { message: 'Password must contain uppercase, lowercase, and number', }) password: string; } // Query DTO with defaults and transformation export class FindUsersQueryDto { @IsOptional() @IsString() @MaxLength(100) search?: string; @IsOptional() @Type(() => Number) @IsInt() @Min(1) @Max(100) limit: number = 20; @IsOptional() @Type(() => Number) @IsInt() @Min(0) offset: number = 0; } // Param validation export class UserIdParamDto { @IsUUID('4') id: string; } @Controller('users') export class UsersController { @Post() create(@Body() dto: CreateUserDto): Promise { // dto is guaranteed to be valid return this.usersService.create(dto); } @Get() findAll(@Query() query: FindUsersQueryDto): Promise { // query.limit is a number, query.search is sanitized return this.usersService.findAll(query); } @Get(':id') findOne(@Param() params: UserIdParamDto): Promise { // params.id is a valid UUID return this.usersService.findById(params.id); } } ``` Reference: [NestJS Validation](https://docs.nestjs.com/techniques/validation)