Skip to main content

Command Palette

Search for a command to run...

NestJS- Elegent way to create backend applications -series #2

Published
8 min read

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
}

image.png

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
}

image.png

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
}

image.png

Lastly lets try with passing some non-numeric for phoneNumber.

{
    "firstName": "John",
    "lastName": "Doe",
    "email": "john.doe@gmail.com",
    "phoneNumber" : "abcd123456789"
}

image.png

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

  1. Data Transfer Object(DTO) - Which provides way to pass object within processes. It eases our development efforts and makes our code clean.
  2. 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...

NestJS- Elegent way to create backend applications -series #2