NestJS course Β· Module 10: Data Validation

Nested Validation - Deep Inspection

3 min read
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.cohorts is an array of valid CohortDto
  • Whether each CohortDto.soldiers is an array of valid SoldierDto
  • Whether each SoldierDto.primaryWeapon is a valid WeaponDto

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!');
59

Spotted 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. 1. What does the @ValidateNested() decorator in class-validator do?

  2. 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:

Useful articles