Subha Chanda Subha is a web developer who is passionate about learning and experimenting with new things.

Understanding object validation with Joi in NestJS

5 min read 1662

Red Lion Head Logo Over Leafy Backround

Table of Contents

When building a robust and scalable API, it’s important to implement proper validation within your application not only in terms of security but also to keep your database clean. If you’re working with robust backend APIs, NestJS can be a great tool to use, and, you can add object validation with NestJS and Joi to make your application secure.

This article will show you how to implement Joi object validation in NestJS via a basic CRUD example.

What is NestJS?

Heavily inspired by Angular, NestJS is an especially popular framework for building scalable and robust applications. It provides a clean architecture based on modules, controllers, and providers to help you get started. It supports TypeScript, but you can also write your applications with vanilla JavaScript.

NestJS doesn’t impose any programming paradigm on you. You are free to use object-oriented, functional, or functional reactive programming. It also provides an excellent routing mechanism under the hood and natively supports the HTTP server framework Express. You can also configure your NestJS application to use Fastify.

The building blocks of NestJS

NestJS has three main building blocks: modules, controllers, and providers.

  • Modules: modules are used to modularize the codebase and split it into reusable components. TypeScript files that are grouped are described with the @Module decorator to provide metadata. NestJS uses these files to organize the application structure
  • Controllers: controllers control the incoming requests and send the appropriate response to the client
  • Providers: providers are the fundamental concept of NestJS. Services, factories, helpers, repositories, etc., are treated as providers in Nest. Providers can be injected as a dependency in Nest

Why use NestJS?

In recent years, Node.js has become a popular programming language, and, because developers want to write production-ready APIs faster, the demand for a quality backend framework has also increased. Enter NestJS, which helps developers write efficient codes faster.

Let’s discuss the benefits of using NestJS.

  • TypeScript: NestJS uses TypeScript by default, but you can also write your NestJS code with vanilla JavaScript
  • Microservices: NestJS supports GraphQL, WebSockets, gRPC, and MQTT, along with REST APIs. The support for these tools helps to write microservices
  • CLI: Nest also has its own CLI, which enables you to create, run, build your applications, and more
  • High-quality documentation: NestJS is well documented and helps you to understand the concepts in depth. It even offers official courses created by NestJS team members
  • Testing: NestJS provides support for testing your application with Jest, or any other testing framework of your choice. NestJS even provides a testing package of its own

What is Joi?

All developers know that it’s vital to validate data coming from clients. If you have ever used MongoDB and mongoose in Node.js, you are likely familiar with mongoose schemas. Mongoose schemas help describe the data and easily add validators for the data. Joi is very similar to schemas.

Joi is a widely used Node.js data validation library that provides a simple, intuitive, and readable API to describe data. It’s primarily used to validate data sent from API endpoints and allows you to create blueprints of the data type you want to accept.

We made a custom demo for .
No really. Click here to check it out.

Here is a simple example of describing schema with Joi:

 const schema = Joi.object().keys({ 
     name: Joi.string().alphanum().min(3).max(30).required(),
     birthyear: Joi.number().integer().min(1970).max(2013), 
});

Building a basic API with NestJS

NestJS provides many options to scaffold code according to your needs, such as the CRUD recipe. This allows you to scaffold a CRUD with endpoints within a few seconds from the Nest CLI.

To install the Nest CLI on your computer, run the following command:

npm i -g @nestjs/cli  

The next step is to generate a Nest application. The Nest CLI uses the following command:

nest new project-name

Here, project-name is the name of the project. After the command completes, run the following command to scaffold the CRUD endpoints:

nest g resource users

It’ll ask you a few questions, such as which transport layer to use. Once you choose the options according to your preference, Nest will scaffold the CRUD API. For example, an API with the users endpoint will be generated from the above command. You can see the new users folder.

Now, if you run the application with npm run start:dev, you’ll see the endpoints logged in the console. Your server will be started at port 3000.

You can either check the endpoints by visiting them or opening the users.controllers.ts file. This file contains the routes for the CRUD API. The services for each API are defined in the users.service.ts file, and all these files are under the users folder.

The importance of object validation

If you look at the GET method for finding a single item in the users.controllers.ts file, you’ll find that there is no validation set up. You can use anything as an ID, and Nest will not throw a validation error.

The OWASP top ten list of security risks mentions that injection attack is still one of the most popular security risks. OWASP also mentions that an application is vulnerable to injection attacks when “user-supplied data is not validated, filtered, or sanitized by the application.”

This clearly shows that data validation is an important security concern to keep in mind when building applications. There are built-in pipes that can verify or modify the input. NestJS has eight built-in pipes. If you want the ID to be only of integer type, you can use the ParseIntPipe pipe. Here’s an example:

@Get(':id')
findOne(@Param('id', ParseIntPipe) id: string) {
  return this.usersService.findOne(+id);
}

If you try to hit the endpoint with any ID other than a numeric value, you’ll receive the following error.

Built-in Pipes Validation with NextJS

Using a built-in pipe is simple, but using it for a large schema is complicated. Joi makes it easier to design schemas and implement validation in NestJS. Let’s implement Joi for the NestJS project.

Implementing Joi in NestJS

Generally, any script that can be injected into NestJS is the Pipe class. Pipes primarily have two use cases:

  • Transforming input data
  • Validating input data

You can read more about pipes in the official documentation.

The first step is to install the necessary packages. Here, only the Joi package is required. Run the following command to install the package.

npm i joi

Now, create a new file called validation.pipe.ts inside the users directory. Creating a custom pipe to implement validation is pretty straightforward. Here’s a code snippet to help you understand.

import {
  PipeTransform,
  BadRequestException,
    ArgumentMetadata
} from '@nestjs/common';

export class ValidationPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    return value;
  }
}

Any schema passed into this pipe constructor will be checked for the configured Joi validation schema. To make the above validator work, open the create-user.dto.ts file inside the dto folder.

Here, define a schema type that the API will use when saving the data. For simplicity, assume that the schema sent by the user and held by the database have the same structure.

Let’s assume the API takes firstname, lastname, email, isVerified, and phoneNumber as input. The DTO will look like this:

export class CreateUserDto {
  public firstname: string;
  public lastname: string;
  public isVerified: boolean;
  public email: string;
  public phoneNumber: number;
}

Now, define the Joi schema inside the user.dto.js file. You can also use separate files to store the schemas. The Joi user schema is simple for this example.

import Joi from 'joi';

export const UserSchema = Joi.object({
  firstname: Joi.string().required(),
  lastname: Joi.string().required(),
  email: Joi.string().email().required(),
  isVerified: Joi.boolean().required(),
  phoneNumber: Joi.number(),
}).options({
  abortEarly: false,
});

The schema is pretty self-explanatory. The string() method ensures that the input is of type string, and the required() method makes certain the fields are inside the input. Similarly, boolean and number make sure the types are boolean or number.

The options method takes other options inside an object. The abortEarly method, when set to true, stops the validation when it finds the first error. Otherwise, it returns all the errors.

Now that the schemas are ready, it is time to update the validation pipe accordingly.
Here is the complete validation.pipe.ts file.

import { PipeTransform, BadRequestException } from '@nestjs/common';

import { CreateUserDto } from './dto/create-user.dto';

import { UserSchema } from './dto/user.dto';

export class CreateUserValidatorPipe implements PipeTransform<CreateUserDto> {
  public transform(value: CreateUserDto): CreateUserDto {
    const result = UserSchema.validate(value);
    if (result.error) {
      const errorMessages = result.error.details.map((d) => d.message).join();
      throw new BadRequestException(errorMessages);
    }
    return value;
  }
}

The custom validator class accepts and returns the CreateUserDto class. The const result = UserSchema.validate(value); validates the result according to the defined Joi schema. If the result has any errors, the results are mapped using the map method. The error messages are joined together. Finally, the error messages are sent to the client. Otherwise, it returns the input value.

If the input passes the validation, it’ll show the message, “This action adds a new user,” according to the method defined inside the user.service.ts file.

We’ve now implemented Joi into NestJS. You can see if the validation is working by sending the JSON payload to the endpoint http://localhost:3000/users.

Validation in Joi NextJS

The above image shows what it looks like when the payload passes the validation. Similarly, the image below shows different errors based on the validation.

Validation Errors in Joi

Conclusion

This article provided an overview of NestJS and Joi and the importance of validation in our apps, then walked you through implementing validation within a NestJS application. I hope you found it useful.

Remember: it is essential to implement proper validation methods to build a robust application. You can check out the Joi and NestJS documentation to better understand the library and frameworks.

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Subha Chanda Subha is a web developer who is passionate about learning and experimenting with new things.

Leave a Reply