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
}
]
}
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[];
}
Below are the validations we need to apply to the mentioned payload:
- Employee
name
should only contain characters i.e. numeric values and symbols not allowed. -
age
should be integer value and greater than (>=) 18 and less than (<=) 65. -
address
has below restrictions:-
country
,state
,street
andcity
should only consist of characters. -
flatNo
should be integer
-
-
projects
should be an array of string. - All the mentioned details must be provided i.e. empty values not allowed.
- 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[];
}
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
Top comments (0)