DEV Community

Fredy Daniel Flores Lemus
Fredy Daniel Flores Lemus

Posted on

Effective Validations in NestJS: Flawless Software Development!

Twitter: https://twitter.com/fredydlemus
Instagram: https://www.instagram.com/fredydlemus/

Validating the data that enters your application through your APIs stands as a pivotal task in the role of a backend developer. This procedure not only fortifies the security of your application but also enhances the user experience and fosters a robust system. Implementing data validation can mitigate potential risks and ensure the smooth operation of your application, making it a critical aspect to consider for achieving overall excellence in backend development.

NestJS offers a sophisticated system that allows developers to conduct these validations both cleanly and elegantly. In this blog post, we will delve deep into the methods of implementing such validation processes. So, without further ado, let's embark on this insightful journey.

Implementing Validations on NestJS

To kickstart our journey, we will initiate a new project utilizing the NestJS CLI. We've decided to name this venture 'validation-behind-scenes'. This descriptive name not only encapsulates the essence of our project but also paves the way for a structured and insightful exploration into the world of NestJS validation processes.

nest new validation-behind-scenes

With our project accessible in a code editor, the next step is to head to our app.controller.ts file to craft a new POST route. This is where we will extract the body contained within the request, harboring the critical intention of validating that the incoming data adheres to our established criteria.

For this demonstration, I envision this route serving a pivotal role in facilitating the creation of new user accounts, thus playing a fundamental part in expanding our application's user base.

import { Controller, Post, Body } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Post()
  createUser(@Body() body: any) {}
}
Enter fullscreen mode Exit fullscreen mode

In the preceding code snippet, it's evident that we are extracting the body content through the utilization of the @Body decorator. Yet, it's crucial to note that we have not instituted any form of validation at this juncture. This lapse allows users to enter any data into our application indiscriminately, presenting a significant security risk. To fortify our application's defenses and initiate the implementation of validations, we are compelled to integrate two vital libraries into our project: 'class-transformer' and 'class-validator'. These libraries will serve as the cornerstone for establishing a secure and reliable validation process.

npm i --save class-validator class-transformer

Upon successful installation of these two pivotal libraries, we will advance to the next step of crafting a class referred to as a DTO, or Data Transfer Object. Within this specialized class, we undertake the task of delineating the specific properties and types that the body of our request should embody when we are in the process of creating a new user. This structured approach ensures a coherent and secure data handling procedure, paving the way for a smooth user creation process.

A DTO, or Data Transfer Object, is a simple object that is used to transfer data between software application layers. It does not contain any business logic but only attributes (data fields) with their respective getters and setters (methods to retrieve and update the values of these attributes).

Think of it as a container for data that needs to be moved from one place to another within a system. It helps to streamline and simplify the data transfer process, promoting cleaner code and easier maintenance.

Within the 'src' directory, I am set to establish a new folder denominated as 'dtos'. Nestled within this folder, I will forge a file dubbed 'create-user.dto.ts'. This file will house a class, which will bear the straightforward yet descriptive name 'CreateUserDTO'. This naming convention maintains clarity and directly reflects the purpose and functionality of the class, facilitating ease of understanding and future reference in the project.

import { IsEmail, IsString } from 'class-validator';

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

  @IsString()
  password: string;
}
Enter fullscreen mode Exit fullscreen mode

As illustrated in the code, we import the decorators @IsEmail and @IsString from the 'class-validator' package. These decorators play a crucial role in specifying the validation criteria that the attributes within our DTO class must adhere to. They facilitate a streamlined validation process by enforcing the respective data types and formats that are expected, thus ensuring the integrity and security of the data handled within our application.

Now comes the pivotal moment where we synthesize all the elements to forge an effective validation mechanism. This endeavor entails two additional steps to fully realize our objective. Initially, we must incorporate the 'CreateUserDTO' into our controller, establishing it as the designated type for our body parameter. This action facilitates the meticulous screening of input data, aligning it with the criteria we have set forth in the DTO class.

import { Controller, Post, Body } from '@nestjs/common';
import { AppService } from './app.service';
import { CreateUserDto } from './dtos/create-user.dto';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Post()
  createUser(@Body() body: CreateUserDto) {}
}

Enter fullscreen mode Exit fullscreen mode

As a result, it is imperative that the body of our request encapsulates an email that conforms to a valid email format, along with a password represented as a string. Failing to meet these criteria will trigger the system to return a '400 Bad Request' response to the user, indicating that the input does not satisfy the necessary conditions, and urging the user to amend the input accordingly

The culmination of our setup process involves one final, vital step. In our main.ts file, we need to implement a global pipe, aptly named 'ValidationPipe', which is sourced from the @nestjs/common package. This particular pipe functions as a facilitator, enabling the validations defined in our body to be actively executed, thereby ensuring that the input data adheres strictly to the established criteria.

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

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

We utilize the 'whitelist: true' setting as a means to guide NestJS in managing unexpected properties in the request body. Specifically, if the body contains properties beyond those stipulated in our DTO (like a 'name' field appearing alongside the 'email' and 'password' fields), this setting will ensure such surplus properties are automatically removed. Consequently, the validation process focuses solely on the parameters defined within our DTO, promoting a clean and streamlined data handling process that adheres to our established structure.

In this blog post, we have navigated through the intricacies of implementing validations within NestJS. Yet, one may wonder, how does specifying our body as our DTO facilitate achieving the desired validation? This underlying mechanism, which forms the crux of the validation process, warrants further exploration. Stay tuned, as we unravel this concept in greater depth in my forthcoming blog post. 😎

Top comments (1)

Collapse
 
tonytkachenko profile image
Tony Tkachenko • Edited

Hmm, the article created from the official documentation... For what? Everyone can read it.
I expect to see here something like zod or valibot + Nest