The in-built NestJS ValidationPipe is a great way to handle validations in an applications.

Validations are an extremely important aspect of any production-level application. Often, developers have to write large amounts of code to handle even basic validations. This is largely counter-productive.

With NestJS ValidationPipe, handling several validations becomes much easier. In this post, we look at how to use the in-built ValidationPipe.

So let’s begin.

1 – NestJS ValidationPipe

ValidationPipe is similar to other in-built pipes available with NestJS. In case you want to know more about the basics of NestJS Pipes, please refer to this post.

Apart from ValidationPipe, other pipes such as ParseIntPipe, ParseBoolPipe, ParseUUIDPipe can also be used for validation purpose. However, ValidationPipe makes use of the powerful class-validator package. Due to this, we can setup validations in a declarative manner.

To begin with ValidationPipe, we need to first install the class-validator package. Below is the command.

npm i --save class-validator class-transformer

You can read more about class-validator from its official site.

2 – Setting up Auto Validation

We will start by setting up auto validation using ValidationPipe.

As a first step, we bootstrap the ValidationPipe in the bootstrap function available in main.ts. See below example:

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

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe())
  await app.listen(3000);
}
bootstrap();

Here, we call the useGlobalPipes() method. Basically, we pass it an instance of the ValidationPipe class. Notice here that ValidationPipe is part of the @nestjs/common package.

To test our pipe, let’s create a simple controller as below.

import { Body, Controller, Post } from "@nestjs/common";
import { CreateUserModel } from "./create-user-model";

@Controller()
export class UserController {

    @Post("/user")
    create(@Body() createUserModel: CreateUserModel) {
        console.log("New User Created");
    }

}

As you can see, it is a simple controller. It basically accepts a model to create a new user. Let’s see how the model looks like.

import { IsEmail, IsNotEmpty } from "class-validator";

export class CreateUserModel {
    @IsEmail()
    email: string;

    @IsNotEmpty()
    password: string;
}

Notice the decorators @IsEmail() and @IsNotEmpty(). These decorators basically represent the validation rules for particular fields. In other words, email field should contain a valid email id. And the password field should not be empty. These decorators are part of the class-validator package.

For example, if an incoming request with invalid email hits our endpoint, the response would be as below:

{
"statusCode": 400,
"message": [
  "email must be an email"
],
"error": "Bad Request"
}
info

INFO

It is important to use concrete classes such as CreateUserModel in the interfaces. This is because typescript does not store metadata about generics or interfaces

3 – ValidationPipe with Request Parameters

We can also use ValidationPipe with other request parameters such as a path parameter.

Check out the below controller.

@Get("/user/:id")
get(@Param() params: SearchParams) {
    console.log("User fetched");
}

Here, we have a path variable id. SearchParams is a simply a DTO class similar to our previous example.

import { IsNumberString } from "class-validator";

export class SearchParams {
    @IsNumberString()
    id: number;
}

The decorator @IsNumberString ensures that only numbers are accepted. If we call the endpoint /user/xyz, we get the below error.

{
"statusCode": 400,
"message": [
  "id must be a number string"
],
"error": "Bad Request"
}

4 – Disabling Error Details in ValidationPipe

Usually, it is a good practice to make your application error messages more informative. However, in some production requirements, detailed error messages are not needed.

In such cases, we can disable the error messages. Basically, we have to tweak the configuration of the ValidationPipe object.

See below example:

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

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe({
    disableErrorMessages: true,
  }))
  await app.listen(3000);
}
bootstrap();

5 – Stripping or Whitelisting Properties

We can also strip unwanted properties from incoming requests. In other words, we can whitelist certain properties.

See below DTO class.

import { IsEmail, IsNotEmpty } from "class-validator";

export class CreateUserModel {
    @IsEmail()
    email: string;

    @IsNotEmpty()
    password: string;

    address: string;
}

Here, address field is not annotated. Basically, we have not whitelisted this property. We want this to be stripped from the payload even if the incoming request contains the same.

To achieve so, we can simply add another configuration parameter in ValidationPipe as below:

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

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe({
    disableErrorMessages: true,
    whitelist: true
  }))
  await app.listen(3000);
}
bootstrap();

See the whitelist: true property in the configuration object. In this case, the request will be processed and the non-whitelisted properties will be stripped off.

Alternatively, we can also make the request fail. Doing so is again a simple matter of configuration. See below example.

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

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe({
    whitelist: true,
    forbidNonWhitelisted: true
  }))
  await app.listen(3000);
}
bootstrap();

Now, if we call the end-point with extra property, we get the below error.

{
"statusCode": 400,
"message": [
  "property address should not exist",
  "email must be an email"
],
"error": "Bad Request"
}

The first message is telling us to remove the address property from the input payload.

Conclusion

With this, we have got a detailed view on how to use the in-built NestJS ValidationPipe.

In another post, we will look at how to transform request payload using NestJS Mapped Types.

If you have any comments or queries, please mention in the comments section below.

Categories: BlogNestJS

Saurabh Dashora

Saurabh is a Software Architect with over 12 years of experience. He has worked on large-scale distributed systems across various domains and organizations. He is also a passionate Technical Writer and loves sharing knowledge in the community.

0 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *