DEV Community

Cover image for NestJS Payload Validation
Shubham Kadam
Shubham Kadam

Posted on • Edited on

NestJS Payload Validation

While working on building APIs, we started talking about how we can validate user input and since we are using NestJS, I started looking into in-house solution for the same. So, I started going through NestJS documentation, looking for a potential solution.

The NestJS documentation is very well written, and I was able to come up with a solution using ValidationPipe in couple of days. But, to build something simple, going through the documentation becomes a bit tedious. The aim of this blog post is to help you get started (with input validation) quickly if you are trying to build something less complex or rather, you can consider this as a beginner’s guide.

Note:
Since this blog is not about NestJS itself, we will proceed, assuming you know how to write microservices using NestJS and what DTOs are.


Before we begin, here’s the link to the github repo for the NestJS project which has the below mentioned code example for you to try locally.

Now, lets assume that we have written a microservice that handles employee details and you want to add new employee details via POST request. The request payload would look like:

{
   "name":"Peter Griffin",
   "age":44,
   "address":{
      "country":"US",
      "state":"California",
      "city":"Los Angeles",
      "street":"Alameda Street",
      "flatNo":12
   },
   "projects":[
      "CustomerInsights",
      "Matter"
   ],
   "workExperience":[
      {
         "orgName":"SomeFamousOrg",
         "exp":5
      },
      {
         "orgName":"SomeSuperFamousOrg",
         "exp":7
      }
   ]
}
Enter fullscreen mode Exit fullscreen mode

DTOs for above payload would look like below:

export class Address {
  country: string;
  state: string;
  city: string;
  street: string;
  flatNo: number;
}

export class WorkExperience {
  orgName: string;
  exp: number;
}

export class EmployeeDetails {
  name: string;
  age: number;
  address: Address;
  projects: string[];
  workExperience: WorkExperience[];
}
Enter fullscreen mode Exit fullscreen mode

Below are the validations we need to apply to the mentioned payload:

  1. Employee name should only contain characters i.e. numeric values and symbols not allowed.
  2. age should be integer value and greater than (>=) 18 and less than (<=) 65.
  3. address has below restrictions:
    1. country, state, street and city should only consist of characters.
    2. flatNo should be integer
  4. projects should be an array of string.
  5. All the mentioned details must be provided i.e. empty values not allowed.
  6. There should be length related restriction on string values.

To get this job done, we will use validation decorators provided by the class-validator package.

Installation command for class-validator package:

~ npm install class-validator --save
The DTOs after changes (along with the imports) would look like below:

import {
  ArrayNotEmpty,
  IsArray,
  IsInt,
  IsNotEmpty,
  IsString,
  Matches,
  MaxLength,
  ValidateNested,
  Min,
  Max,
  IsNumber,
} from 'class-validator';
import { Type } from 'class-transformer';

export class Address {
  @IsString()
  @IsNotEmpty()
  @Matches('^[a-zA-Z\\s]+$')
  @MaxLength(15)
  country: string;

  @IsString()
  @IsNotEmpty()
  @Matches('^[a-zA-Z\\s]+$')
  @MaxLength(15)
  state: string;

  @IsString()
  @IsNotEmpty()
  @Matches('^[a-zA-Z\\s]+$')
  @MaxLength(15)
  city: string;

  @IsString()
  @IsNotEmpty()
  @Matches('^[a-zA-Z\\s]+$')
  @MaxLength(20)
  street: string;

  @IsInt()
  @IsNotEmpty()
  flatNo: number;
}

export class WorkExperience {
  @IsString()
  @IsNotEmpty()
  @Matches('^[a-zA-Z0-9\\s]+$')
  @MaxLength(30)
  orgName: string;

  @IsNumber({ maxDecimalPlaces: 2 })
  @IsNotEmpty()
  exp: number;
}

export class EmployeeDetails {
  @IsNotEmpty()
  @IsString()
  @Matches('^[a-zA-Z\\s]+$')
  @MaxLength(50)
  name: string;

  @IsNotEmpty()
  @IsInt()
  @Min(18)
  @Max(65)
  age: number;

  @ValidateNested()
  @Type(() => Address)
  @IsNotEmpty()
  address: Address;

  @IsArray()
  @ArrayNotEmpty()
  @IsString({ each: true })
  @Matches('^[a-zA-Z0-9\\s]+$', undefined, { each: true })
  @MaxLength(30, { each: true })
  projects: string[];

  @IsArray()
  @ArrayNotEmpty()
  @ValidateNested({ each: true })
  @Type(() => WorkExperience)
  workExperience: WorkExperience[];
}
Enter fullscreen mode Exit fullscreen mode

Explanation

The validation for input values of name and age is straightforward. Let’s look at the attributes which are a bit complex.

projects:

projects attribute is of type array i.e. array of string, so the decorators @IsArray() and @ArrayNotEmpty() were applied accordingly.

But, how do we validate values inside the array? For example, if we have an array:

projects: [‘CustomerInsights’, ‘DemoPipeline’]

How do we validate values ‘CustomerInsights’ and ‘DemoPipeline’ individually? How do we make sure that they satisfy all the necessary restrictions?

The answer is, by passing validation option each: true inside the desired decorator.

why? Because,

If your field is an array and you want to perform validation of each item in the array you must specify a special each: true decorator option.
NestJS Documentation

We want the array values to be string, so we use @IsString() decorator and pass argument each: true to it, i.e. @IsString({ each: true }). Similar approach is applied to other decorators like @Matches() and @MaxLength() as well.

address:

The address attribute is not of primitive type, but instead, is an object which consists of nested object. Due to this, we applied @ValidateNested() decorator along with @Type() decorator, to indicate object type and applied validation separately to each of its nested objects (refer class Address).

workExperience:

The workExperience is similar to the address attribute as far as its type is concerned, the only difference is, instead of consisting of nested objects, it represents ‘array of nested objects‘, and hence, we added the each: true option to the @ValidateNested() decorator i.e.@ValidateNested({ each: true }) and this will ensure that all the nested objects are validated.


And we are done! Hope you guys enjoyed it.
In my next blog, I’ll talk about how to perform custom payload validation.

Please do checkout the project's github repo

Done

Stay tuned, Thank You!

Top comments (0)