NestJS: Validate request body based on one property

Updated: December 31, 2023 By: Guest Contributor Post a comment

Learn to validate a request body conditionally based on a property value in NestJS, enhancing your application’s robustness and data integrity.

Introduction to Validation in NestJS

In NestJS, request validation is an essential part of creating robust and secure applications. It ensures that incoming data meets the expected format and value criteria before processing it. NestJS leverages class-validator and class-transformer libraries to provide declarative validation that aligns seamlessly with the framework’s module-based architecture. In this tutorial, we will dive deep into conditional validation, focusing on how to trigger validations based on specific property values within a request body.

Basic Conditional Validation

class-validator offers a straightforward way to perform object-level property validation. The first step is to define your validation rules within a DTO (Data Transfer Object). Here’s an example:

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

class MyAppRequestDTO {
  @IsNotEmpty()
  @IsString()
  action: string;

  @ValidateIf(o => o.action === 'create')
  @IsNotEmpty()
  payload: any;
}

With the above code, the `payload` property will only be checked for being not empty if the `action` property equals ‘create’.

Creating Custom Decorators for Validation

When predefined decorators are not sufficient, you can create custom decorators in NestJS. Here’s a simple custom decorator that checks if a property exists based on another property’s value:

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

function IsPropertyConditional(property: string, validationOptions?: ValidationOptions) {
  return function (object: Object, propertyName: string) {
    registerDecorator({
      name: 'isPropertyConditional',
      target: object.constructor,
      propertyName: propertyName,
      constraints: [property],
      options: validationOptions,
      validator: {
        validate(value: any, args: ValidationArguments) {
          let relatedValue = args.object[args.constraints[0]];
          return relatedValue ? value !== null && value !== undefined : true;
        }
      }
    });
  };
}

This decorator will ensure that the marked property exists only when the specified condition is met.

Advanced Conditional Validation Scenarios

For more complex validation scenarios, you might need to perform an asynchronous operation or access a service within your custom validation decorator. Imagine needing to check if a username is unique only when a user decides to change it. Such a process would involve reaching out to the database, which can be asynchronous:

import { Injectable } from '@nestjs/common';
import { ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments } from 'class-validator';

class UniqueUsernameValidator implements ValidatorConstraintInterface {
  constructor(private readonly userService: UserService) {}

  async validate(username: string, args: ValidationArguments) {
    if (args.object['changeUsername'] === true) {
      return await this.userService.isUsernameUnique(username);
    }
    return true;
  }
}

Incorporating your custom logic into decorators like this allows for rich, real-world data validation flows including database integration. However, managing dependencies for your custom validators requires setting up provider injections, which brings additional complexity and the need to configure the global module in NestJS.

Putting It All Together

Once you’ve established your validation rules, integrate them with NestJS’s `Pipe` mechanism. Apply the `ValidationPipe` to your route handlers to automatically enforce the rules:

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

@Controller('my-app')
export class MyAppController {
  constructor(private readonly myAppService: MyAppService) {}

  @Post()
  @UsePipes(new ValidationPipe())
  async handleAction(@Body() requestDto: MyAppRequestDTO) {
    //... perform your business logic
  }
}

When a request is made to your endpoint, the `ValidationPipe` will automatically enforce the conditional validation rules you’ve defined in your DTO.

Summary

NestJS’s advanced validation capabilities offer a powerful way to ensure data integrity and protect your application from invalid data. Mastering conditional validation helps in covering a wide array of user interactions with confidence, providing flexible and bulletproof data validation strategies. Follow this tutorial’s concepts to boost the reliability of your server-side validations significantly.