Validation & DTOs
Never Trust User Input
Open interactive version (quiz + challenge)Real-world analogy
DTOs are like airport security checkpoints. Before you board a plane (enter the server), your luggage (data) goes through scanners (validators). Is your bag the right size? Any prohibited items? If anything is wrong, you get stopped BEFORE you reach the plane. Without security (validation), anyone could bring anything on board — chaos!
What is it?
DTOs (Data Transfer Objects) define the expected shape of request data. Combined with class-validator, they automatically validate incoming data — checking types, formats, lengths, and custom rules. NestJS ValidationPipe connects DTOs to the request pipeline, rejecting invalid data with helpful error messages before it reaches your business logic.
Real-world relevance
Every form submission, API call, and webhook payload should be validated. Without validation, users could send malformed data that crashes your database, inject malicious code, or bypass business rules. DTOs are your first line of defense.
Key points
- What is a DTO? — Data Transfer Object — a class that defines the shape of incoming data. CreateUserDTO says 'to create a user, you MUST send name, email, and password with these rules.' Nothing more, nothing less.
- class-validator Decorators — @IsString(), @IsEmail(), @MinLength(8), @IsNotEmpty(), @IsOptional(), @IsNumber(), @IsArray(), @IsEnum(). Decorators on DTO properties that define validation rules.
- ValidationPipe — NestJS built-in pipe that automatically validates incoming data against your DTO. Add it globally: app.useGlobalPipes(new ValidationPipe()). Invalid requests get a clean 400 error.
- Whitelist & Transform — Setting whitelist: true on ValidationPipe strips any properties not defined in your DTO, preventing attackers from injecting extra fields like isAdmin: true. Setting transform: true auto-converts types so a string '42' from a URL parameter becomes the number 42. Always enable both in production apps.
- Nested Validation — DTOs can contain other DTOs for complex nested data. Use @ValidateNested() and @Type(() => AddressDTO) to validate objects within objects. This ensures every level of deeply nested request bodies is thoroughly validated, not just the top-level properties. Essential for complex forms and nested API payloads.
- Custom Validators — When built-in decorators are not enough, create custom validators with @ValidatorConstraint(). Build reusable rules like @IsStrongPassword (checks complexity requirements) or @IsUniqueEmail (queries the database to verify uniqueness). Custom validators keep your validation logic centralized and reusable across all DTOs.
- Partial & Pick Types — PartialType(CreateUserDTO) makes all fields optional — perfect for UpdateUserDTO where users might only change one field. PickType selects specific fields from an existing DTO. OmitType removes fields. IntersectionType combines multiple DTOs. These utilities let you compose DTOs without repeating property definitions.
- Validation Groups — Validation groups let you apply different rules depending on the context. For example, an email field might be required during registration but optional during profile updates. Assign groups to decorators and specify which group to validate in each route — one DTO handles multiple validation scenarios cleanly.
- Error Messages & i18n — Customize validation error messages with the message option: @MinLength(8, { message: 'Password must be at least 8 characters' }). For international apps, integrate i18n libraries to return error messages in the user's preferred language. Clear, localized error messages dramatically improve the user experience.
Code example
// Install: npm install class-validator class-transformer
// create-user.dto.ts
import {
IsString, IsEmail, MinLength,
IsOptional, IsEnum, IsNotEmpty
} from 'class-validator';
enum UserRole { ADMIN = 'admin', USER = 'user' }
export class CreateUserDTO {
@IsString()
@IsNotEmpty()
@MinLength(2)
name: string;
@IsEmail()
email: string;
@IsString()
@MinLength(8)
password: string;
@IsOptional()
@IsEnum(UserRole)
role?: UserRole;
}
// update-user.dto.ts — all fields optional!
import { PartialType } from '@nestjs/mapped-types';
export class UpdateUserDTO extends PartialType(CreateUserDTO) {}
// user.controller.ts
import { Body, Post, Put, Param } from '@nestjs/common';
@Post()
async create(@Body() dto: CreateUserDTO) {
// dto is already validated! Safe to use.
return this.userService.create(dto);
}
@Put(':id')
async update(@Param('id') id: string, @Body() dto: UpdateUserDTO) {
return this.userService.update(id, dto);
}
// main.ts — Enable validation globally
import { ValidationPipe } from '@nestjs/common';
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({
whitelist: true, // strip unknown properties
transform: true, // auto-transform types
forbidNonWhitelisted: true, // error on unknown props
}));
// What happens when invalid data is sent:
// POST /users { name: "", email: "not-email", hack: "injected" }
// Response 400:
// {
// "statusCode": 400,
// "message": [
// "name must be longer than or equal to 2 characters",
// "email must be an email",
// "property hack should not exist"
// ],
// "error": "Bad Request"
// }Line-by-line walkthrough
- 1. Install: npm install class-validator class-transformer
- 2.
- 3. create-user.dto.ts
- 4. Importing required dependencies
- 5.
- 6.
- 7.
- 8.
- 9. Defining an enum
- 10.
- 11. Exporting for use in other files
- 12. Decorator that adds metadata or behavior
- 13. Decorator that adds metadata or behavior
- 14. Decorator that adds metadata or behavior
- 15.
- 16.
- 17. Decorator that adds metadata or behavior
- 18.
- 19.
- 20. Decorator that adds metadata or behavior
- 21. Decorator that adds metadata or behavior
- 22.
- 23.
- 24. Decorator that adds metadata or behavior
- 25. Decorator that adds metadata or behavior
- 26.
- 27. Closing block
- 28.
- 29. update-user.dto.ts — all fields optional!
- 30. Importing required dependencies
- 31. Exporting for use in other files
- 32.
- 33. user.controller.ts
- 34. Importing required dependencies
- 35.
- 36. Decorator that adds metadata or behavior
- 37.
- 38. dto is already validated! Safe to use.
- 39. Returning a value
- 40. Closing block
- 41.
- 42. Decorator that adds metadata or behavior
- 43.
- 44. Returning a value
- 45. Closing block
- 46.
- 47. main.ts — Enable validation globally
- 48. Importing required dependencies
- 49.
- 50. Declaring a variable
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57. What happens when invalid data is sent:
- 58. POST /users { name: "", email: "not-email", hack: "injected" }
- 59. Response 400:
- 60. {
- 61. "statusCode": 400,
- 62. "message": [
- 63. "name must be longer than or equal to 2 characters",
- 64. "email must be an email",
- 65. "property hack should not exist"
- 66. ],
- 67. "error": "Bad Request"
- 68. }
Spot the bug
export class CreateUserDTO {
@IsString()
name: string;
@IsEmail()
email: string;
@MinLength(8)
password: string;
}Need a hint?
What type decorator is missing from the password field?
Show answer
Password has @MinLength(8) but is missing @IsString() decorator. Best practice: always include the type decorator. Fix: add @IsString() above @MinLength(8).
Explain like I'm 5
Imagine filling out a form to join a club. The form says 'Name: at least 2 letters' and 'Age: must be a number'. If you write something wrong, the form tells you what to fix BEFORE you hand it in. DTOs are like those smart forms that check your answers!
Fun fact
The 2017 Equifax data breach exposed 147 million people's personal data. The root cause? A known vulnerability in an unpatched server — but proper input validation and security layers could have limited the damage significantly.
Hands-on challenge
Create a CreateProductDTO with: name (string, min 3 chars), price (number, positive), category (enum: electronics/clothing/food), description (optional string, max 500 chars). Set up ValidationPipe globally and test with invalid data!
More resources
- NestJS Validation (NestJS Official)
- class-validator Documentation (GitHub)
- class-transformer Documentation (GitHub)