SoFunction
Updated on 2025-04-14

NestJS uses class-validator for data verification

Preface

In modern web development, data verification is an indispensable part. It not only ensures the accuracy of data, but also improves the security of the system. When using the NestJS framework for project development, the two libraries class-validator and class-transformer provide us with convenient data verification solutions.

This article will use detailed steps and practical skills to guide you how to use class-validator for data verification in NestJS. Through this article, you will be able to learn how to use class-validator to elegantly implement data verification, as well as 11 commonly used verification techniques in actual combat to improve the data verification ability of the project.

Steps to use

Step 1: Install class-validator and class-transformer

To use class-validator, two libraries need to be installed: class-validator and class-transformer.

npm install class-validator class-transformer

Step 2: Create DTO (Data Transfer Object)

In NestJS, DTO (Data Transfer Object) is usually used to define the structure of requested data. First, you need to create a DTO class for user registration and use the class-validator decorator to define the validation rules.

// src/user/dto/
import { IsString, IsEmail, IsNotEmpty, Length } from 'class-validator';

export class CreateUserDto {
  @IsString()
  @IsNotEmpty()
  @Length(4, 20)
  username: string;

  @IsEmail()
  email: string;

  @IsString()
  @IsNotEmpty()
  @Length(8, 40)
  password: string;
}

In this DTO, three fields are defined: username, email, and password, and the verification rules are specified using the class-validator's decorator.

Step 3: Use pipeline to verify data

Next, you need to use DTO in the controller and verify the incoming data through NestJS's pipeline (Pipes).

// src/user/
import { Controller, Post, Body } from '@nestjs/common';
import { CreateUserDto } from './dto/';

@Controller('user')
export class UserController {
  @Post('register')
  async register(@Body() createUserDto: CreateUserDto) {
    // Handle registration logic    return { message: 'User registered successfully', data: createUserDto };
  }
}

In this example, the @Body() decorator is used in the register method to get the request body and pass in CreateUserDto. NestJS automatically verifies the DTO, and if the verification fails, an exception is thrown and an appropriate error response is returned.

Step 4: Enable the verification pipeline globally

For more convenient management, the verification pipeline can be enabled globally so that all DTO verifications will be performed automatically.

// src/
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './';

async function bootstrap() {
  const app = await (AppModule);
  (new ValidationPipe());
  await (3000);
}

bootstrap();

In the file, the verification pipeline is enabled globally using ValidationPipe. This way, NestJS will automatically perform data verification no matter which controller you use DTO. Of course, you can only enable the verification pipeline for some controllers. For details, please refer to the practical skills below.

Practical usage skills

1. Local verification pipeline

Verification pipelines can be configured for specific routing or controller methods without global activation. This allows for flexibly using different verification rules in different scenarios.

import { Controller, Post, Body, UsePipes, ValidationPipe } from '@nestjs/common';
import { CreateUserDto } from './dto/';

@Controller('user')
export class UserController {
  @Post('register')
  @UsePipes(new ValidationPipe({
    transform: true,
    whitelist: true,
    forbidNonWhitelisted: true,
  }))
  async register(@Body() createUserDto: CreateUserDto) {
    // Handle registration logic    return { message: 'User registered successfully', data: createUserDto };
  }
}

2. Custom error message

class-validator allows custom error messages to be defined for each validation rule. For example:

import { IsString, IsNotEmpty, Length, IsEmail } from 'class-validator';

export class CreateUserDto {
  @IsString({ message: 'The username must be a string' })
  @IsNotEmpty({ message: 'Username cannot be empty' })
  @Length(4, 20, { message: 'The username must be between 4 and 20 characters' })
  username: string;

  @IsEmail({}, { message: 'The email format is incorrect' })
  email: string;
  
  @IsString({ message: 'The password must be a string' })
  @IsNotEmpty({ message: 'Password cannot be empty' })
  @Length(8, 40, { message: 'The password must be between 8 and 40 characters' })
  password: string;
}

3. Nested Object Verification

If the DTO contains nested objects, you can use the @ValidateNested() decorator for verification. For example:

import { Type } from 'class-transformer';
import { ValidateNested, IsString, IsNotEmpty } from 'class-validator';

class AddressDto {
  @IsString()
  @IsNotEmpty()
  street: string;

  @IsString()
  @IsNotEmpty()
  city: string;
}

export class CreateUserDto {
  @IsString()
  @IsNotEmpty()
  username: string;

  @ValidateNested()
  @Type(() => AddressDto)
  address: AddressDto;
}

4. Combination Verification Decorator

Sometimes multiple validation rules may need to be combined together, and you can use @ValidatorConstraint() to create a custom validation decorator. For example:

Determine whether the user name already exists in the database

import { registerDecorator, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface } from 'class-validator';

@ValidatorConstraint({ async: false })
export class IsUsernameUniqueConstraint implements ValidatorConstraintInterface {
  validate(username: any) {
    // Here you can add verification logic, such as querying the database    return true; // If verification passes, return true  }
}

export function IsUsernameUnique(validationOptions?: ValidationOptions) {
  return function (object: Object, propertyName: string) {
    registerDecorator({
      target: ,
      propertyName: propertyName,
      options: validationOptions,
      constraints: [],
      validator: IsUsernameUniqueConstraint,
    });
  };
}

// Use a custom decoratorexport class CreateUserDto {
  @IsUsernameUnique({ message: 'The username already exists' })
  username: string;
}

2. Verify password strength

import { registerDecorator, ValidationOptions, ValidationArguments } from 'class-validator';
export function IsStrongPassword(validationOptions?: ValidationOptions) {
  return function (object: Object, propertyName: string) {
    registerDecorator({
      name: 'isStrongPassword',
      target: ,
      propertyName: propertyName,
      options: validationOptions,
      validator: {
        validate(value: any, args: ValidationArguments) {
          return /(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}/.test(value);
        },
        defaultMessage(args: ValidationArguments) {
          return 'The password must contain upper and lower case letters, numbers and special characters and is at least 8 characters long';
        },
      },
    });
  };
}
export class ChangePasswordDto {
  @IsStrongPassword({ message: 'The password does not meet the strength requirements' })
  newPassword: string;
}

3. Condition verification

Verify based on the conditions, you can use the @ValidateIf decorator.

import { ValidateIf, IsNotEmpty, IsEmail } from 'class-validator';
export class UpdateUserDto {
  @IsEmail()
  email: string;
  @ValidateIf(o => )
  @IsNotEmpty({ message: 'The new email address cannot be empty' })
  newEmail: string;
}

4. Use @Matches for regular expression verification

Using the @Matches decorator, you can verify that the string matches the specified regular expression.

import { Matches } from 'class-validator';
export class ChangePasswordDto {
  @Matches(/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}/, { message: 'The password must contain upper and lower case letters, numbers and special characters and is at least 8 characters long' })
  newPassword: string;
}

5. Global verification options

When enabling the verification pipeline globally, you can configure global verification options, such as stripping non-whitelist fields, automatic conversion types, etc.

// src/
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './';
async function bootstrap() {
  const app = await (AppModule);
  (new ValidationPipe({
    whitelist: true, // Strip the non-whitelist field    forbidNonWhitelisted: true, // Non-whitelist fields are prohibited    transform: true, // Automatic conversion type  }));
  await (3000);
}
bootstrap();

6. Dynamic verification message

Sometimes it may be necessary to generate error messages dynamically based on specific verification conditions, which can be implemented using ValidationArguments.

import { IsString, MinLength, ValidationArguments } from 'class-validator';

export class CreateUserDto {
  @IsString()
  @MinLength(4, {
    message: (args: ValidationArguments) => {
      return `The username is too short,At least it is required ${[0]} Characters`;
    },
  })
  username: string;
}

7.@Validate Custom Verification Logic

If the built-in decorator does not meet the needs, you can use the @Validate decorator to add custom verification logic.

import { Validate, ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments } from 'class-validator';

@ValidatorConstraint({ name: 'customText', async: false })
class CustomTextConstraint implements ValidatorConstraintInterface {
  validate(text: string, args: ValidationArguments) {
    return ('prefix_'); // Any custom logic  }

  defaultMessage(args: ValidationArguments) {
    return 'text ($value) Must be "prefix_" beginning';
  }
}

export class CustomTextDto {
  @Validate(CustomTextConstraint)
  customText: string;
}

8. Attribute grouping verification

By grouping, different fields can be verified in different situations. For example, different fields may need to be validated when creating and updating.

import { IsString, IsNotEmpty } from 'class-validator';

export class CreateUserDto {
  @IsString()
  @IsNotEmpty({ groups: ['create'] })
  username: string;

  @IsString()
  @IsNotEmpty({ groups: ['create', 'update'] })
  password: string;
}

// Specify the group when usingimport { ValidationPipe } from '@nestjs/common';

const createUserValidationPipe = new ValidationPipe({ groups: ['create'] });
const updateUserValidationPipe = new ValidationPipe({ groups: ['update'] });

Verify using different pipelines in the controller:
import { Controller, Post, Put, Body, UsePipes } from '@nestjs/common';
import { CreateUserDto } from './dto/';
import { createUserValidationPipe, updateUserValidationPipe } from './validation-pipes';

@Controller('user')
export class UserController {
  @Post('create')
  @UsePipes(createUserValidationPipe)
  async createUser(@Body() createUserDto: CreateUserDto) {
    // Handle the creation of user logic    return { message: 'User created successfully', data: createUserDto };
  }

  @Put('update')
  @UsePipes(updateUserValidationPipe)
  async updateUser(@Body() updateUserDto: CreateUserDto) {
    // Handle update user logic    return { message: 'User updated successfully', data: updateUserDto };
  }
}

9. Only perform some attribute verification

Sometimes it may be necessary to verify only a portion of the properties of the object, which can be implemented using PartialType.

import { PartialType } from '@nestjs/mapped-types';

export class UpdateUserDto extends PartialType(CreateUserDto) {}
//The following is how to use UpdateUserDto in the controller.// src/user/
import { Controller, Post, Put, Body, Param } from '@nestjs/common';
import { CreateUserDto } from './dto/';
import { UpdateUserDto } from './dto/';
@Controller('user')
export class UserController {
  @Post('create')
  async createUser(@Body() createUserDto: CreateUserDto) {
    // Handle the creation of user logic    return { message: 'User created successfully', data: createUserDto };
  }
  @Put('update/:id')
  async updateUser(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
    // Handle update user logic    return { message: 'User updated successfully', data: updateUserDto };
  }
}

In this example, UpdateUserDto inherits from PartialType(CreateUserDto), which means that UpdateUserDto contains all the properties in CreateUserDto, but these properties are optional. This is very useful in update operations, because we may want to provide only those fields that need to be updated, not all fields.

10. Internationalization of verification messages

The internationalization of verification messages can be achieved by using custom verification decorators and message generation functions.

import { IsString, IsNotEmpty, Length, ValidationArguments } from 'class-validator';
import { i18n } from 'i18next'; // Assume that i18n is used in the project
export class CreateUserDto {
  @IsString()
  @IsNotEmpty({ message: (args: ValidationArguments) => ('') })
  @Length(4, 20, { message: (args: ValidationArguments) => ('', { min: 4, max: 20 }) })
  username: string;
}

Summarize

Using class-validator combined with NestJS allows easy data verification in applications, which not only improves the readability of the code, but also ensures the accuracy and security of the data.

The above is the detailed content of NestJS using class-validator for data verification. For more information about NestJS data verification, please follow my other related articles!