NestJS- Elegent way to create backend applications -series #2
Background
In our first blog ( NestJS- Elegent way to create backend applications -series #1 ), we went through high level concepts of NestJS framework. We created basic application using Nest cli, added simple APIs to create customer, fetch customer using id and get all customers. We touch based upon concepts of Modules, Controllers and Services. We used these concepts in our application.
In this blog we will go through concepts of
Data Transfer Objects(DTO)
Validation
Lets explore these concepts and use them in our application.
Data Transfer Object (DTO)
Open file src/customers/customers.service.ts and look at createCustomer method.
import { Injectable } from '@nestjs/common';
import { v4 as uuidv4 } from 'uuid';
@Injectable()
export class CustomersService {
private customers = [];
getAllCustomers() {
return this.customers;
}
getCustomerById(id: string) {
return this.customers.filter((d) => d.id === id);
}
createCustomer(firstName: string, lastName: string, email: string) {
const customerObject = {
id: uuidv4(),
firstName: firstName,
lastName: lastName,
email: email,
};
return this.customers.push(customerObject);
}
}
In createCustomer method we have 3 parameters, firstName, lastName and email. To call this method from controller, we have to pass these 3 parameters.
Now think that we need to add another attribute for our customer. let say phone number. We have to make changes at two places.
customers.service.ts : createCustomer method
In controller where we are calling createCustomer method.
This is repetitive, isn't it? Can we simplify it?
Yes, using Data Transfer Object(DTO).
As per Wikipedia, Data Transfer Object (DTO) is an object which carries data between processes.
Lets define DTO for our create customer API. Create sub-directory called 'dto' in customers directory. Inside newly created 'dto' directory, create new file with name 'create-customer.dto.ts' and add below code
export class CreateCustomerDto {
firstName: string;
lastName: string;
email: string;
phoneNumber: number;
}
Now we have our dto created and added additional attribute phoneNumber.
Lets use it in service. Update createCustomer method with below code, Our create customer method in customers.service.ts will look like
createCustomer(createCustomerDto: CreateCustomerDto) {
const {firstName, lastName, email, phoneNumber} = createCustomerDto;
const customerObject = {
id: uuidv4(),
firstName: firstName,
lastName: lastName,
email: email,
phoneNumber: phoneNumber
};
return this.customers.push(customerObject);
}
We have replaced all parameters with createCustomerDto. Inside method we are getting all properties from dto and using it.
Note: You need to import createCustomerDto in service file.
In file customers.controller.ts You need to change method
@Post()
createCustomer(@Body() createCustomerDto: CreateCustomerDto) {
return this.customerServices.createCustomer(createCustomerDto);
}
If we see now controller and service for createCustomer, it looks much simpler. we do not need to update multiple places for any additional change.
This is use of Data Transfer Object (DTO).
You can test APIs to make sure everything is working as expected
Validation
Now we have our APIs working properly but there is no validation done on inputs. Lets add some basic validation. There is concept of ' Pipes ' in NestJS, as name suggest it provides two use cases:
Validation: Validate input
Transform: Transform input into specific format before it is processed.
We will be using it for validation. NestJS provides some out-of-the-box pipes. We will be using ValidationPipe out of it.
Lets install required packages
yarn add class-validator class-transformer
Once packages are installed, lets go to our dto file: customers/dto/create-customer.dto.ts
Add basic validation decorators for our properties. Our file will look like.
import { IsEmail, IsInt, IsNotEmpty, IsString } from 'class-validator';
export class CreateCustomerDto {
@IsString()
@IsNotEmpty()
firstName: string;
@IsString()
@IsNotEmpty()
lastName: string;
@IsEmail()
email: string;
@IsInt()
phoneNumber: number;
}
We can see we have used decorators like IsString, IsNotEmpty, IsEmail, IsInt from class-validators.
Now to use ValidationPipe at application level, we need to update main.ts.
Updated main.ts will look like below, we have added statement "app.useGlobalPipes(new ValidationPipe());"
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 are making sure to use ValidationPipe at application level. So that validation will be done for all controllers. You can specify to apply validation at controller level or even at method level.
Lets run our application and see if validation is working
yarn run start:dev
Try to create customer with firstName as empty string. Pass below as body for API.
{
"firstName": "",
"lastName": "Doe",
"email": "john.doe@gmail.com",
"phoneNumber" : 123456789
}

You got error that firstName should not be empty.
This is awesome. Here Nest took care of responding with proper error message and 400 status code.
Lets pass both firstName, lastName as empty
{
"firstName": "",
"lastName": "",
"email": "john.doe@gmail.com",
"phoneNumber" : 123456789
}

Here we got an array of errors with clear message of what fields are not passed for validation.
Lets try for an email field
{
"firstName": "John",
"lastName": "Doe",
"email": "john.doe",
"phoneNumber" : 123456789
}

Lastly lets try with passing some non-numeric for phoneNumber.
{
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@gmail.com",
"phoneNumber" : "abcd123456789"
}

Awesome, our validation is working as expected. Please try by changing inputs or adding few more decorators for input and see behavior.
Summary
In this blog we covered two important concepts
- Data Transfer Object(DTO) - Which provides way to pass object within processes. It eases our development efforts and makes our code clean.
- Validation: Validation by just adding decorators to our attributes and Nest does all heavy lifting for us. Most of time existing decorators will be enough for data validation. Even you can create your custom validator for specific use case and extend functionality.
Github Link for project: github.com/vishwasrao/simplecrm
Stay Tuned for next blog in series!!!
Happy Learning...