So far, we've been validating simple, flat objects. But what if incoming data has nested objects or arrays of objects? We need deep inspection — the guards must check not only the person at the gate, but also their luggage, documents in the bag, and items in the pockets.
Imagine a DTO for a legion that contains a list of legionaries:
1// WITHOUT nested validation
2export class CreateLegioDto {
3 @IsString()
4 name: string;
5
6 @IsArray()
7 soldiers: CreateSoldierDto[]; // Array of objects
8 // But is each object in the array valid? WE DON'T KNOW!
9}@IsArray() alone only checks whether the value is an array — it won't validate the contents of the objects inside!The
@ValidateNested() decorator tells class-validator: "Also validate the inner object":1import { ValidateNested } from 'class-validator';
2import { Type } from 'class-transformer';
3
4class CreateSoldierDto {
5 @IsString()
6 @IsNotEmpty()
7 name: string;
8
9 @IsNumber()
10 @Min(16)
11 age: number;
12}
13
14export class CreateLegioDto {
15 @IsString()
16 @IsNotEmpty()
17 name: string;
18
19 @ValidateNested()
20 @Type(() => CreateSoldierDto)
21 commander: CreateSoldierDto;
22}Important:
@ValidateNested() always requires @Type() from class-transformer! Without it, NestJS doesn't know which class to convert the raw object to.When you have an array of objects, you must add
{ each: true }:1export class CreateLegioDto {
2 @IsString()
3 name: string;
4
5 @ValidateNested({ each: true })
6 @Type(() => CreateSoldierDto)
7 soldiers: CreateSoldierDto[];
8}The
{ each: true } parameter tells the decorator to check each element of the array separately. Without it, nested validation won't work on arrays.Nesting can go many levels deep:
1class WeaponDto {
2 @IsString()
3 @IsNotEmpty()
4 name: string;
5
6 @IsNumber()
7 @Min(1)
8 damage: number;
9}
10
11class SoldierDto {
12 @IsString()
13 @IsNotEmpty()
14 name: string;
15
16 @ValidateNested()
17 @Type(() => WeaponDto)
18 primaryWeapon: WeaponDto;
19
20 @IsOptional()
21 @ValidateNested()
22 @Type(() => WeaponDto)
23 secondaryWeapon?: WeaponDto;
24}
25
26class CohortDto {
27 @IsString()
28 name: string;
29
30 @ValidateNested({ each: true })
31 @Type(() => SoldierDto)
32 soldiers: SoldierDto[];
33}
34
35export class CreateLegioDto {
36 @IsString()
37 name: string;
38
39 @ValidateNested({ each: true })
40 @Type(() => CohortDto)
41 cohorts: CohortDto[];
42}In this example, NestJS will validate:
CreateLegioDto.cohorts is an array of valid CohortDtoCohortDto.soldiers is an array of valid SoldierDtoSoldierDto.primaryWeapon is a valid WeaponDtoWhen nested validation fails, NestJS returns a detailed error with the path to the problematic field:
1// Example error response:
2{
3 "statusCode": 400,
4 "message": [
5 "cohorts.0.soldiers.1.name should not be empty",
6 "cohorts.0.soldiers.1.primaryWeapon.damage must be at least 1"
7 ],
8 "error": "Bad Request"
9}Deep inspection is a key skill for gate guards. Without it, a malicious user could smuggle invalid data inside nested objects!