NestJS course Β· Module 10: Data Validation
Nested Validation - Deep Inspection
In this lesson5
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.
The Problem - Flat Validation Is Not Enough
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!
@ValidateNested() - the nested object guard
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.
Validating Arrays of Nested Objects
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.
Multi-Level Nesting
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:
- Whether
CreateLegioDto.cohortsis an array of validCohortDto - Whether each
CohortDto.soldiersis an array of validSoldierDto - Whether each
SoldierDto.primaryWeaponis a validWeaponDto
Nested Error Messages
When 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!
Code for this lesson: src/nested-validation.ts
1// Nested validation - Deep inspection
2import {
3 IsString,
4 IsNotEmpty,
5 IsNumber,
6 Min,
7 Max,
8 IsArray,
9 ArrayMinSize,
10 ValidateNested,
11 IsOptional,
12 IsEnum,
13} from 'class-validator';
14import { Type } from 'class-transformer';
15
16// Weapon DTO
17class WeaponDto {
18 @IsString()
19 @IsNotEmpty()
20 name: string;
21
22 @IsNumber()
23 @Min(1)
24 @Max(100)
25 damage: number;
26}
27
28// TODO: Complete the soldier DTO with nested validation
29class SoldierDto {
30 @IsString()
31 @IsNotEmpty()
32 name: string;
33
34 @IsNumber()
35 @Min(16)
36 age: number;
37
38 // TODO: Add @ValidateNested() and @Type(() => WeaponDto)
39 primaryWeapon: WeaponDto;
40
41 // TODO: Add @IsOptional(), @ValidateNested(), @Type(() => WeaponDto)
42 secondaryWeapon?: WeaponDto;
43}
44
45// TODO: Complete the cohort DTO with an array of nested objects
46class CohortDto {
47 @IsString()
48 @IsNotEmpty()
49 name: string;
50
51 // TODO: Add @ValidateNested({ each: true })
52 // TODO: Add @Type(() => SoldierDto)
53 // TODO: Add @IsArray() and @ArrayMinSize(3)
54 soldiers: SoldierDto[];
55}
56
57console.log('Nested validation checks objects in depth!');
58console.log('Remember: @ValidateNested requires @Type!');
59Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. What does the @ValidateNested() decorator in class-validator do?
2. Why does @ValidateNested() require @Type() from class-transformer?
These are 2 of 3 questions for this lesson. Solve the rest in the game.
Hands-on tasks in the game
- Code editor
Complete the nested validation decorators in SoldierDto and CohortDto
- Vertical ordering
Arrange the nested validation levels from outermost to innermost:
- Click in order
Arrange the decorators for the soldiers field in CreateLegioDto in the correct order: