Introduction
Validating incoming data is crucial for any backend application. In NestJS, nested object validation can ensure the integrity and structure of the data your app handles. This tutorial will explore how to apply validation rules seamlessly in your NestJS projects.
Setting Up Validation
Before diving into nested object validation, it’s imperative to set up the foundational validation package. NestJS leans on the class-validator
package to enable decorators for validation rules. To begin, install the required packages with:
npm install class-validator class-transformer
Once installed, you’ll also need to integrate them into your module using ValidationPipe
. Include the pipe globally in your main application file:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
Basic Validation with DTO Classes
Data Transfer Object (DTO) classes are a primary way to define and validate the structure of the data in a NestJS app. For a simple object with a single level of properties, use class-validator
decorators to describe the constraints:
import { IsString, IsInt, Min, Max } from 'class-validator';
class CreateUserDto {
@IsString()
readonly name: string;
@IsInt()
@Min(0)
@Max(100)
readonly age: number;
}
This DTO ensures that name
is a string, and age
is an integer between 0 and 100.
Validating Nested Objects
Let’s consider a case with nested objects:
import { IsString, IsInt, Min, Max, ValidateNested, IsOptional, Type } from 'class-validator';
class AddressDto {
@IsString()
readonly street: string;
@IsInt()
readonly zipCode: number;
}
class CreateUserDto {
@IsString()
readonly name: string;
@IsInt()
@Min(0)
@Max(100)
readonly age: number;
@Type(() => AddressDto)
@ValidateNested()
@IsOptional()
readonly address?: AddressDto;
}
In the above example, Type
and ValidateNested
decorators are used to handle nested object validation. The Type
decorator indicates which class to transform the nested object into, while ValidateNested
enforces the validation rules defined inside the nested DTO class.
Advanced Validation Techniques
For more complex scenarios, you can employ custom validators or use conditional validation using IsNotEmpty
, IsOptional
, and arrow functions to apply validation criteria flexibly.
Custom Validators
Here’s an example of creating a custom validator:
import { ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments } from 'class-validator';
@ValidatorConstraint({ name: 'customTextLength', async: false })
class CustomTextLengthValidator implements ValidatorConstraintInterface {
validate(text: string, args: ValidationArguments) {
return text.length > 10 && text.length < 20; // custom validation criteria
}
defaultMessage(args: ValidationArguments) {
return 'Text length is wrong';
}
}
Decorate the property in your DTO with the custom validator:
class CreateUserDto {
//... other properties
@Validate(CustomTextLengthValidator)
readonly bio: string;
}
Conditional Validation
Use conditions to apply validation rules only when certain conditions are met:
import { IsString, ValidateIf } from 'class-validator';
class CreateUserDto {
//... other properties
@IsString()
@ValidateIf(o => o.bio !== '')
readonly nickname: string;
}
This ensures that nickname
must meet the IsString
validation rule only if bio
is not empty.
Groups and Nested Validation
Groups allow you to control which validation rules apply at different times. They are handy for cases when specific fields must be validated only in certain contexts, like during creation or update operations.
Define groups in your DTO:
import { IsString, IsInt, Min, Max, ValidateNested, GroupSequenceProvider, GroupSequence } from 'class-validator';
@GroupSequence(['CreateUserDto', 'FullCheck'])
class CreateUserDto {
//... other properties
@IsString({ groups: ['FullCheck'] })
readonly detailedBio: string;
}
Then specify groups when using the validation pipe:
//... setup code
app.useGlobalPipes(new ValidationPipe({ groups: ['FullCheck'] }));
Custom Transformers
Apart from using built-in decorators from class-transformer
, you can also define custom transformers to preprocess your data before validation occurs.
Conclusion
Validating nested objects in NestJS using class-validator ensures structured and predictable data throughout your application. By utilizing the validation decorators and techniques we’ve covered, you can create robust and secure NestJS applications tailored to your data requirements. Embrace these features to keep your application’s input solid and your server operations reliable.