Guide to Nest.js - Building a REST API with Nest and Node

As a Node.js backend developer, you will agree that by default, Node.js is very bare bones, and makes no assumptions about what you need while building an app. As a result, you are in charge of setting up everything that you want to use across an app, including handling routing, making API calls, setting up TypeScript or Web Sockets, or even fundamental things like code organization, file structure, and naming conventions.

Managing a large-scale application can be a difficult task, especially if it was not designed with a clear structure and strict code organization guidelines.

Nest.js tries to tackle some of these problems by creating an abstraction around Node.js so that you as a developer can focus on the application problem rather than other tiny implementation details.

In this guide, you will learn the core fundamentals of Nest.js from top to bottom, aimed at getting you up to speed so that you can build enterprise-grade Node.js applications with the help of Nest.js in no time.

Everything we will learn through this guide will be incremental; covering a lot of ground on introductory concepts. To get the most out of this guide, it helps to code along.

Let us dive right in, folks!

Source Code: As usual, you can fork and tinker with the source code hosted on GitHub.

Note: We'll be using Postman to test the API in our demo. You can download it on the Postman Download page. Alternatively, you can simply use the browser, the command-line curl tool, or any other tool you might be familiar with.

What is Nest.js

Think of Nest.js as a superset of Node.js that abstracts away difficult tasks, tools, and boilerplate code, while also adding a full-fledged toolkit for your application development using modern JavaScript and TypeScript.

Nest.js provides an out-of-the-box application architecture that allows developers and teams to create highly scalable, testable, loosely coupled, and easily maintainable, by leveraging readily available and prominent options and modules in the community, like those available in Express.js applications. You could even swap Express (which it uses under the hood by default) for Fastify, but doing so would mean that you may need to use different Fastify-compliant libraries in your application.

It combines the features of Functional Programming, Object Oriented Programming, and Functional Reactive Programming, and with more than 52.4k stars and 6.2k forks on GitHub and a weekly download count of up to 1,784,004, the progressive Node.js framework is a popular go-to for crafting efficient, scalable, and enterprise-grade server-side applications.

Features of Nest.js

The following are reasons why Nest.js has grown to become such a popular Node.js framework:

  1. Nest.js was created to help developers to build both monolithic applications and microservices too.
  2. While it is powerful, it is also developer-friendly to work with; easy to use, quick to learn, and easy to apply.
  3. It leverages TypeScript (a superset of JavaScript) out of the box and makes room for developers to write maintainable code free from runtime errors.
  4. It possesses a Command Line Interface that helps to boost the productivity of developers and ease of development.
  5. When building with Nest.js, development processes are enhanced and time is saved whether you are bootstrapping a Minimum Viable Product or working on an application because Nest comes with an amazing project folder structure by default.
  6. It supports a variety of Nest-specific modules that help in the integration of common concepts and technologies including TypeORM, GraphQL, logging, validation, Mongoose, WebSockets, caching, etc.
  7. Nest.js can boast of holding some of the best documentation for any framework out there. Its documentation is thorough, easy to understand, and helpful in saving debugging time, as it comes through effortlessly when there is a need for a solution to a problem.
  8. Nest.js integrates with Jest, which makes it simple to write unit tests on your applications.
  9. It is built for both small and large-scale enterprise applications.

Creating a Nest.js Project

To get started with Nest.js on your local machine, you first have to install the Nest Command Line Interface (CLI), which would help to scaffold a new Nest.js project folder and populate the folder with core files and modules needed for a Nest.js application.

Run the following command to install the Nest.js Command Line Interface:

$ npm i -g @nestjs/cli
// Or
$ yarn global add @nestjs/cli
// Or
$ pnpm add -g @nestjs/cli

Once you have successfully installed the Nest.js CLI globally on your local machine, you can run nest on the command line to see various commands that we can tap into:

$ nest

Results in:

Usage: nest <command> [options]

Options:
  -v, --version                                   Output the current version.
  -h, --help                                      Output usage information.

Commands:
  new|n [options] [name]                          Generate Nest application.
  build [options] [app]                           Build Nest application.
  start [options] [app]                           Run Nest application.
  info|i                                          Display Nest project details.
  add [options] <library>                         Adds support for an external library to your project.
  generate|g [options] <schematic> [name] [path]  Generate a Nest element.
    Schematics available on @nestjs/schematics collection:
      ┌───────────────┬─────────────┬──────────────────────────────────────────────┐
      │ name          │ alias       │ description                                  │
      │ application   │ application │ Generate a new application workspace         │
      │ class         │ cl          │ Generate a new class                         │
      │ configuration │ config      │ Generate a CLI configuration file            │
      │ controller    │ co          │ Generate a controller declaration            │
      │ decorator     │ d           │ Generate a custom decorator                  │
      │ filter        │ f           │ Generate a filter declaration                │
      │ gateway       │ ga          │ Generate a gateway declaration               │
      │ guard         │ gu          │ Generate a guard declaration                 │
      │ interceptor   │ itc         │ Generate an interceptor declaration          │
      │ interface     │ itf         │ Generate an interface                        │
      │ middleware    │ mi          │ Generate a middleware declaration            │
      │ module        │ mo          │ Generate a module declaration                │
      │ pipe          │ pi          │ Generate a pipe declaration                  │
      │ provider      │ pr          │ Generate a provider declaration              │
      │ resolver      │ r           │ Generate a GraphQL resolver declaration      │
      │ service       │ s           │ Generate a service declaration               │
      │ library       │ lib         │ Generate a new library within a monorepo     │
      │ sub-app       │ app         │ Generate a new application within a monorepo │
      │ resource      │ res         │ Generate a new CRUD resource                 │
      └───────────────┴─────────────┴──────────────────────────────────────────────┘

Here, you are shown how to make use of the commands, and can now tap into the new|n [options] [name] command to create your very first Nest.js project:

$ nest new getting-started-with-nestjs
// Or
$ nest n getting-started-with-nestjs

Next, you will be asked what package manager you would like to use:

? Which package manager would you ❤️ to use? (Use arrow keys)
  npm
  yarn
> pnpm

Feel free to choose the package manager of your choice, I will go with pnpm. This is because it is about three times more efficient and faster than NPM, and with a speedy cache system, PNPM is also faster than Yarn.

After choosing a package manager, the installation process continues, then the Nest.js app would be created.

Now, you can cd into the newly created project, and open it with an editor of your choice:

$ cd getting-started-with-nestjs

With the project now created, we can run it with either of the following commands:

$ npm run start
// Or
$ yarn start
// Or
$ pnpm run start

If you take a look at the package.json file, you will notice in the script segment, the value for pnpm run start is nest start:

// package.json
    
"start": "nest start",

This means that you can also run the Nest.js app by running:

$ nest start

A Look at the Nest.js Project Structure

Let us have a close look at how a Nest app is structured:

/package.json

The package.json file is the heart of the Node.js and by extension, Nest.js project. It holds all metadata about the project and defines various functional properties of the project that are needed to install application dependencies or run project scripts.

We have already seen the ability of the start script.

The start:dev profile makes it possible to watch for changes in the application and automatically reload it, without the need to stop the application and restart it - and it's meant for development. The start:prod script is useful when you want to test whether your application is production-ready as well as when you deploy it to production, along with other scripts for testing the Nest.js app.

@nestjs/platform-express defines express as the default HTTP server in a Nest application.

/tsconfig.json

The tsconfig.json file is a file written in JSON (JavaScript Object Notation) that defines TypeScript-related options required to compile the Nest app.

/nest-cli.json

This holds metadata that is needed to build, organize or deploy Nest applications.

/test

This directory holds all files needed to run Nest tests. Nest uses the Jest framework for testing with Jest configuration in the jest-e2e.json file.

/src

The src directory is the parent folder for the core of the Nest project. It holds the main.ts file which is the file where the Nest app starts. The job of the main.ts file is to load AppModule that is imported from /src/app.module.ts.

Later in this guide, we will learn about Modules; one of the major components of a Nest.js application.

The AppModule is a class that is created as a module, using the @Module decorator. In the app.module.ts file, AppService from ./app.service and AppController from ./app.controller are also imported.

The AppController is also a class that is created using the @Controller decorator, while the AppService is a class that is created using the @Injectable annotation.

The cool thing about Nest is that it has very few decorators within that add metadata to any class and that metadata defines the purpose of that class, such that:

  • @Controller()transforms a class into a controller.
  • @Module() transforms a class into a module.
  • @Injectable() transforms a class into a provider.

Also in the src directory is the app.controller.spec.ts file, which is a test file for Controllers.

We can run the app using nest start.

The app gets started at http://localhost:3000 on your browser:

We can change the content that shows at http://localhost:3000, by heading over to the app.service.ts file, where the provider for the index route was defined.

The Building Blocks of a Nest.js App

There are three major components of a Nest.js application:

  1. Modules
  2. Controllers
  3. Providers

In learning about the building blocks of a Nest app, let us first clean up the Nest project, by deleting the app.controller.spec.ts, ./app.service, app.module.ts, and ./app.controller files; leaving just main.ts, to emulate a from-scratch development lifecycle.

At this point, when we remove the imported AppModule file from main.ts, we are prompted that An argument for 'module' was not provided.

To demonstrate the building blocks of a Nest app, we will take a look at a simple User Profile implementation, by building a REST API to handle CRUD operations on an object.

Modules

In the src folder create a new app.module.ts file, then create an AppModule class, which we export.

Next, import the AppModule class into main.ts, and run nest start.

Navigate to http://localhost:3000 in your browser and you will get a 404 error:

This is because we have not yet defined a route for the base URL of the Nest app.

Back in app.module.ts, we have the AppModule class that we have is not yet a Nest module. To make it a Nest module, we add the @Module() decorator which is imported from @nestjs/commonthen we pass an empty object.

// app.module.ts

import { Module } from '@nestjs/common';
@Module({})

export class AppModule {}

Now, we have a Nest.js module!

Note: A module is a class that is annotated with a @Module() decorator.

Every Nest application has a root module that serves as an entry point to resolve a Nest application’s structure and relationships.

It is highly recommended to use multiple modules to organize your application’s components.

The @Module() decorator makes it possible to allow developers to define metadata about a class in the Nest app.

In the case where there are multiple modules, such as a users module, orders module, chat module, etc, the app.module.ts should be used to register all other modules of the Nest app.

Creating Routes; Controllers

Controllers are needed to create routes in Nest applications. A controller’s purpose is to receive specific requests for a Nest application; controlling the request and response cycle for various routes within the application.

When an HTTP request is made from the client to the Nest application, the route that matches the route wherein the request is being made handles the request and returns the appropriate response.

To create a controller in a Nest app, we have to make use of the @Controller() decorator.

In the src directory, create a new file app.contoller.ts, and therein, we can define a Nest controller:

import { Controller } from '@nestjs/common';

@Controller({})

export class AppController {}

That is it! We have a very nice controller, but to create a new route, we need to first let our Nest app know about the created controller.

To achieve this, we make sure to import AppController in app.module.ts, and define information about the controllers in @Module() decorator - as an array of controllers:

// app.module.ts

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';

@Module({
  controllers: [AppController],
})

export class AppModule {}

Handling GET Requests

Then we define a simple getUser() route (with the @Get() decorator used for handling HTTP GET requests to a specified path) to serve as the base route, we can access the same on the browser at https://localhost:3000:

// app.controller.ts

import { Controller, Get } from '@nestjs/common';

@Controller({})

export class AppController {
  @Get()
  getUser() {
    return 'I am a great person';
  }
}

This results in:

Hmmm, here we are returning just a string, but what if we wanted to return an object? Instead of a string, we can define an object:

// app.controller.ts

import { Controller, Get } from '@nestjs/common';

@Controller({})

export class AppController {
  @Get()
  getUser() {
    return { name: 'Uchechukwu Azubuko', country: 'Nigeria' };
  }
}

Navigate to http://localhost:3000 in your browser and you will see the object:

Away from the base route, how about creating a route similar to http://localhost:3000/user for fetching all users?

We can create a controller to handle such a route in a couple of ways.

One way would be to define a new method, using the @Get() decorator/handler.

import { Controller, Get } from '@nestjs/common';

@Controller({})

export class AppController {
  @Get()
  getUser() {
    return { name: 'Uchechukwu Azubuko', country: 'Nigeria' };
  }
}

Nest.js provides decorators or handlers for all of the various HTTP methods including @Get(), @Post(), @Put(), @Delete(), @Patch(), @Options(), and @Head().

The @All() decorator defines an endpoint that handles all of the various methods.

Handling POST Requests

We can also define POST requests for storing data in the database, using the @Post() decorator:

import { Controller, Post } from '@nestjs/common';

@Controller({})
export class AppController {
  @Post()
  store() {
    return 'Post request successful';
  }
}

Then, we test the POST request using Postman and notice that the string is returned successfully as defined.

You might ask, what if I also want to do more than return data? Perhaps, to send data.

For that, you need to inject the data inside the route method, as shown:

import { Controller, Post, Req } from '@nestjs/common';
import { Request } from 'express';

@Controller({})
export class AppController {
  @Post()
  store(@Req() req: Request) {
    return req.body;
  }
}

Now, when we test the POST request with Postman, we are able to view the data that is being sent. In this case, it’s just an empty object:

Dynamic Routing with Route Parameters

Suppose you want to accept dynamic data as part of a request. First, we need to define the token in the path of the route, in order to note the dynamic position on the route/URL, then using the @Param() decorator, the route parameter can be accessed like so:

import { Controller, Get, Param } from '@nestjs/common';

@Controller({})
export class AppController {
  @Get('/:userId')
  getUser(@Param() userId: number) {
    return userId;
  }
}
Free eBook: Git Essentials

Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!

The userId is returned successfully:

Handling Asynchronous Requests

Nest.js is able to handle asynchronous requests that return a promise using various approaches:

import { Controller, Get} from '@nestjs/common';

@Controller({})
export class AppController {
  @Get()
  async findAll(): Promise<any[]> {
    return [];
  }
}

In the approach above, asynchronicity is handled using the async keyword. Another approach is by returning RxJS observable streams:

import { Controller, Get} from '@nestjs/common';

@Controller({})
export class AppController {
  @Get()
  findAll(): Observable<any[]> {
    return of([]);
  }
}

Here, Nest.js will subscribe to the source under the hood, and when the stream is completed, it will take the last emitted value automatically.

Handling Redirects in Nest

The @Redirect() decorator is used to redirect a response to a different URL. The @Redirect() decorator accepts two arguments - the URL to redirect to and the status code upon redirection, both of which are optional:

import { Controller, Get} from '@nestjs/common';

@Controller({})
export class AppController {
  @Get()
  @Redirect('https://www.ucheazubuko.com', 302)
  getSite() {
    return { url: 'https://stackabuse.com' };
  }
}

Returning Status Code

To return the status code for any request handled on the Nest.js server, the @HttpCode(…) easily comes through.

In Nest, the default status code for GET requests is 200, a POST request is 201, an error request is 304

The status code for a server request can be defined as shown below:

import { Controller, Post, HttpCode } from '@nestjs/common';

@Controller({})
export class AppController {
  @Post()
  @HttpCode(204)
  create() {
    return 'This action adds a new user to the app.';
  }
}

Handling DELETE Requests

Similar to making a POST request, a delete request can be handled like so:

import { Controller, Delete, Param } from '@nestjs/common';

@Controller({})
export class AppController {
  @Delete('/:userId')
  delete(@Param() params: { userId: number }) {
    return params;
  }
}

Handling UPDATE Requests

A request to update specific data on the server can be handled using the @Patch() decorator:

import { Controller, Patch, Req} from '@nestjs/common';
import { Request } from 'express';

@Controller({})
export class AppController {
  @Patch('/:userId')
  update(@Req() req: Request) {
    return req.body;
  }
}

Now that we have seen various ways to define typical controllers that we would often have on a robust server, it is important to note that the controller should be lean, clean, and defined per use case, such that if there is another controller for defining user routes, then a separate directory should be created and dedicated for handling the same - away from the AppController.

Then in user.controller.ts, we can configure all route handlers therein to be prefixed with /user/ by writing code like shown below:

// user/user.controller.ts

import { Controller, Get } from '@nestjs/common';

@Controller('/user')
export class UserController {
  @Get()
  getUser() {
    return 'I am from the user controller';
  }
}

Next, register UserController in the controllers' arrays in app.modules.ts:

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { UserController } from './controllers/user/user.controller';

@Module({
  controllers: [AppController, UserController],
})

export class AppModule {}

When we navigate to https:localhost:3000/user, it returns successfully:

To keep the project folder even neater than it is right now, we can define a user.module.ts file where we will define the UserController:

import { Module } from '@nestjs/common';
import { UserController } from './user.controller';

@Module({
  controllers: [UserController],
})

export class UserModule {}

Then, import UserModule into app.module.ts:

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { UserModule } from './user/user.module';

@Module({
  controllers: [AppController],
  imports: [UserModule],
})

export class AppModule {}

With this, we will be able to have the same effect as previously.

Note: Nest makes it easy to (g)enerate (mo)dules and (co)ntrollers using the nest g mo and nest g co commands. Specific modules, such as the user module and controllers can also be created quickly using the Nest CLI, by running the commands: nest g mo user - to create a user module, and nest g co user - to create a user controller.

Providers

All fetching of data from a database should be handled by providers instead of controllers, to create a layer of abstraction between the user-facing code and the code that interacts with potentially sensitive data. Between these layers - validation can be set up to ensure proper database handling. With the Nest CLI, we can create providers by generating services:

$ nest g s user

This creates a UserService wherein we would define all business logic for the UserController, so that UserController only handles requests and responses. In user.service.ts, we see that the @Injectable() decorator is used to define the class. In Nest, the use of the @Injectable() decorator is to transform services, repositories, or helpers class into a provider.

Providers get injected into a class through its constructor. Let’s take a close look at an example.

Earlier, in user.controller.ts, we had defined the business logic for getting the user object, but now, we should define the same in the UserService:

// user.service.ts

import { Controller, Injectable } from '@nestjs/common';

@Controller({})

export class AppController {
  @Injectable()
  get() {
    return { name: 'Uchechukwu Azubuko', country: 'Nigeria'; };
  }
}

Next, in the user.controller.ts file, let us define a constructor in the UserController class. In this constructor, we provide a private userService, which is a type of the UserService class. It is with this private that we are able to tap into the business logic we had defined earlier for fetching the users:

// user.controller.ts

import { Controller, Get } from '@nestjs/common';
import { UserService } from './user.service';

@Controller('/user')
export class UserController {
  constructor(private userService: UserService) {}
  @Get()
  getUser() {
    return this.userService.get();
  }
}

Thus, the UserController class, now depends on the UserService class in a concept known as
dependency injection.

In the same way, the logic in both user.controller.ts and user.service.ts files are updated accordingly:

// user.controller.ts

import {
  Controller,
  Delete,
  Get,
  Param,
  Patch,
  Post,
  Req,
} from '@nestjs/common';
import { Request } from 'express';
import { UserService } from './user.service';

@Controller('user')
export class UserController {
  constructor(private userService: UserService) {}
  @Get()
  getUsers() {
    return this.userService.get();
  }
  @Get('/:userId')
  getUser(@Param() param: { userId: number }) {
    return this.userService.getUser(param);
  }
  @Post()
  store(@Req() req: Request) {
    return this.userService.create(req);
  }
  @Patch('/:userId')
  update(@Req() req: Request, @Param() param: { userId: number }) {
    return this.userService.update(req, param);
  }
  @Delete()
  delete(@Param() param: { userId: number }) {
    return this.userService.delete(param);
  }
}
// user.service.ts

import { Injectable } from '@nestjs/common';
import { Request } from 'express';

@Injectable()
export class UserService {
  get() {
    return { name: 'Uchechukwu Azubuko', country: 'Nigeria' };
  }
  getUser(param: { userId: number }) {
    return param;
  }
  create(req: Request) {
    return req.body;
  }
  update(req: Request, param: { userId: number }) {
    return { body: req.body, param };
  }
  delete(param: { userId: number }) {
    return param;
  }
}

Now, let us verify that the endpoints work as they ought to, using Postman.

Demystifying Dependency Injection in Nest.js

When building smaller components of an application, such as a class or module, your class may depend on another class or module's functionality, for example, the need to tap into an HTTP service provided by a different class in order to make API calls, or service layers that interact with the persistence layer.

Dependencies can be provided within controllers through dependency injection.

Dependency injection is a programming concept and pattern that expresses how parts of an application are delivered to other parts of the application that require them, in such a way as to provide high cohesion but loose coupling.

Nest supports dependency injection and you can use it in your Nest applications to enhance the modularity of your project.

A practical illustration is depicted like so:

Suppose class A uses some functionality of class B. Then it is said that class A depends on class B. Thus, in order to use class B in class A, we need to create an instance of class B first (that is, creating a Class B object): const b = new B ().
Transferring the task of creating an instance of a class to another class and directly using the dependency in the class being provided for (the injector component) is known as dependency injection.

Advice: Dependency injection, or DI, is one of the fundamental concepts in frameworks like Spring Boot, Nest.js and Angular.js, if you would like to read more about it, you can check the official Angular documentation.

Typically, a class should solely concentrate on fulfilling its functions rather than being used to create various objects that it might require or not.

Benefits of Dependency Injection.

  1. It helps with unit testing.
  2. With dependency injection, boilerplate code is reduced, since the initializing of dependencies is done by the injector component.
  3. The process of extending an application becomes easier.
  4. Dependency injection helps to enable loose coupling.

Exploring Request Payloads

Remember that on various request handlers like POST, and PATCH, we were able to tap into the request that is sent by the server using the @Req() decorator. However, there is more to that.

Rather than retrieve the entire request object, we can just tap into specific parts of the request object that we need.
Thus, Nest provides various decorators that can be used with the HTTP route handlers to access Express of Fastify objects:

Nest decorators Fastify or Express object that is accessed
`@Request(), @Req()` `req`
`@Response(), @Res()` `re``s`
`@Next()` `next`
`@Session()` `req.session`
`@Param(param?: string)` `req.params` / `req.params[param]`
`@Body(param?: string)` `req.body` / `req.body[param]`
`@Query(param?: string)` `req.query` / `req.query[param]`
`@Headers(param?: string)` `req.headers` / `req.headers[param]`
`@Ip()` `req.ip`
`@HostParam()` `req.hosts`

A typical example would be replacing the @Req() decorator which we used previously to get access to the body of the result, with the @Body() which can already give us direct access to the body of a request without drilling:

// user.controller.ts

@Post()
store(@Body() body: any) {
  return this.userService.create(body);
}

@Patch('/:userId')
update(@Body() body: any, @Param() param: { userId: number }) {
  return this.userService.update(body, param);
}
// user.service.ts

create(body: any) {
  return body;
}

update(body: any, param: { userId: number }) {
  return { body: body, param };
}

In some cases, you might only want to retrieve specific properties of a request payload. In that case, you would have to define a Data Transfer Object (DTO) schema. The Data Transfer Schema is an object that defines a copy of the object being retrieved, but is used primarily to transfer the data between the object that's supposed to be saved or retrieved, and the persistence layer. Typically, since this process is more vulnerable to attacks - the DTO doesn't contain as many sensitive data points. This characteristic also allows you to only retrieve certain fields of an object.

In Nest, it is recommended to use classes to define a Data Transfer Object, since the value of classes is preserved during compilation.

Supposing the body of the request had a token, and you do not want to retrieve or update such data, then a DTO can be defined as shown below:

// user.controller.ts

@Patch('/:userId')
update(
  @Body() updateUserDto: { name: string; email: string },
  @Param() param: { userId: number },
) {
  return this.userService.update(updateUserDto, param);
}
// user.service.ts

update(
  updateUserDto: { name: string; email: string },
  param: { userId: number },
) {
  return { body: updateUserDto, param };
}

However, you would notice that we have defined the type for updateUserDto twice; in user.service.ts and in user.controller.ts, but we need to keep our codes DRY (Don't Repeat Yourself) so that we do not repeat ourselves around the codebase.

For this, in a new folder /user/dto in the /user directory, we need to create a file /update-user.dto.ts with the .dto.ts extension where we define and export the UpdateUserDto class for use in the user.service.ts and user.controller.ts files:

// user/dto/update-user.dto.ts

export class UpdateUserDto {
  name: string;
  email: string;
}
// user.controller.ts
...
import { UpdateUserDto } from './dto/update-user.dto';

@Patch('/:userId')
update(
  @Body() updateUserDto: UpdateUserDto,
  @Param() param: { userId: number },
) {
  return this.userService.update(updateUserDto, param);
}
// user.service.ts
...
import { UpdateUserDto } from './dto/update-user.dto';

update(updateUserDto: UpdateUserDto, param: { userId: number }) {
  return { body: updateUserDto, param };
}

Pipe and Validation

Suppose there is a need to validate the data that is gotten when a request has been made over the server.

In Nest, we can test the correctness of any data getting in or out of the application by using pipes installing two dependencies - class-validator and class-transformer.

A pipe is a class that is defined with the @Injectable() decorator (thus, pipes are providers), that implements the PipeTransform interface. They transform data to the desired format and evaluate data such that if the data is found valid, it passes unchanged, else, an exception is thrown. In order to use a pipe, you need to bind an instance of the particular pipe class to the appropriate context.

The class-validator package makes it possible to validate decorators and non-decorators, using validator.js internally. While the class-transformer package makes it possible to transform objects into instances of a class, transform class into object, and serialize or deserialize objects based on certain criteria.

The eight pipes provided by Nest are:

  • ValidationPipe
  • ParseArrayPipe
  • ParseIntPipe
  • ParseUUIDPipe
  • ParseBoolPipe
  • DefaultValuePipe
  • ParseEnumPipe
  • ParseFloatPipe

To demonstrate validation in Nest in this guide, we will use the built-in ValidationPipe that makes it possible to enforce validation on request payloads and combines well with the class-validator package; specific rules are declared with simple annotations in Data Transfer Object/local class declarations in each module.

To begin using the built-in ValidationPipe which is exported from @nestjs/common, let us install the class-validator and class-transformer packages:

$ npm i --save class-validator class-transformer
# Or
$ yarn add class-validator class-transformer
# Or
$ pnpm install class-validator class-transformer

Next, navigate to main.ts where we will bind ValidationPipe at the root level of the application to ensure that all endpoints in our app are protected from retrieving invalid data:

// main.ts

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();

Next, in the Data Transfer Object declarations of each module, we add a few validation rules by declaring the appropriate data checks for each individual data. In our case, we would declare appropriate validation rules for name and email in UpdateUserDto:

// update-user.dto.ts

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

export class UpdateUserDto {
  @IsString()
  name: string;

  @IsEmail()
  email: string;
}

The @IsString() decorator checks if a given data is a real string, and the @IsEmail() validator checks if a given data is an email, else it returns false and throws an exception.

Now, if we attempt to make a PATCH request to a user profile, and input a number instead of a valid email, for example, an exception will be thrown:

With these, we have a very nice validation in our Nest app.

While validating with ValidationPipe, it is also possible to filter our properties that we don’t want our method handler to receive. For example, if our handler only expects name and email properties, but a request also includes a country property, we can remove the country property from the resulting object by setting whitelist to true when we instantiate ValidationPipe:

// main.ts

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({
      whitelist: true,
    }),
  );
  await app.listen(3000);
}

bootstrap();

Binding Pipes at Method Parameter Level

Pipes can also be defined on params, as well. For this, we will bind the pipe at the method’s param level.

Before now, even though we defined the userId to be a number, you would notice that if we make a request with the userId as a string, it turns out successful regardless:

To ensure that the value of userId must always be a number, we will declare it bind the getUser() method handler with a validation check that ensures the same:

// user.controller.ts
...
import { ParseIntPipe } from '@nestjs/common';

@Get('/:userId')
getUser(@Param('userId', ParseIntPipe) userId: number) {
  return this.userService.getUser(userId);
}
// user.service.ts

getUser(userId: number) {
  return { userId };
}

The ParseIntPipe defines the built-in ParseInt Pipe and ensures that the data it is to run against must be an integer.

Now, when we make a GET request to an invalid userId of string “ab”, the validation fails and an exception is thrown with a 400 status code:

But with a numeric value, the validation passes successfully:

We can also update other method handlers accordingly to ensure proper validation:

// user.controller.ts

import {
  Body,
  Controller,
  Delete,
  Get,
  Param,
  ParseIntPipe,
  Patch,
  Post,
  Req,
} from '@nestjs/common';
import { Request } from 'express';
import { UserService } from './user.service';

@Controller('user')
export class UserController {
  constructor(private userService: UserService) {}
  @Get()
  getUsers() {
    return this.userService.get();
  }
  @Get('/:userId')
  getUser(@Param('userId', ParseIntPipe) userId: number) {
    return this.userService.getUser(userId);
  }
  @Post()
  store(@Req() req: Request) {
    return this.userService.create(req);
  }
  @Patch('/:userId')
  update(
    @Body() updateUserDto: { name: string; email: string },
    @Param('userId', ParseIntPipe) userId: number,
  ) {
    return this.userService.update(updateUserDto, userId);
  }
  @Delete()
  delete(@Param('userId', ParseIntPipe) userId: number) {
    return this.userService.delete(userId);
  }
}
// user.service.ts

import { Injectable } from '@nestjs/common';
import { Request } from 'express';
import { UpdateUserDto } from './dto/user-update.dto';

@Injectable()
export class UserService {
  get() {
    return { name: 'Uchechukwu Azubuko', country: 'Nigeria' };
  }
  getUser(userId: number) {
    return { userId };
  }
  create(req: Request) {
    return req.body;
  }
  update(updateUserDto: UpdateUserDto, userId: number) {
    return { body: updateUserDto, userId };
  }
  delete(userId: number) {
    return { userId };
  }
}

Now, we have ensured the best-practice technique for validating data that is getting into our application, perhaps from an external source, at any point in time.

Conclusion

In this guide, you have been able to learn about the latest kid on the Node.js block; Nest.js, and all that is required to help you get started if you desire to build an application with it. You have learned what Nest is, its features, how to create a Nest project, how to handle incoming data into a Nest app, and how to validate the incoming data. In all, you have learned about the building blocks of any Nest application, and the value that each component brings to a Nest.js application.

From this point, there is still so much to learn in regards to building an enterprise-grade application with Nest, but you have been able to successfully cover fundamental concepts that can get you up and running unto all that lies ahead.

Watch out for a new guide in the future, where we learn how to build a restful API with Nest and MySQL.

Thanks for reading!

Additional Resources

Nest.js Docs
Angular Docs

Last Updated: June 19th, 2023
Was this article helpful?

Improve your dev skills!

Get tutorials, guides, and dev jobs in your inbox.

No spam ever. Unsubscribe at any time. Read our Privacy Policy.

Uchechukwu AzubukoAuthor

Uchechukwu Azubuko is a Software Engineer and STEM Educator passionate about making things that propel sustainable impact and an advocate for women in tech.

He enjoys having specific pursuits and teaching people better ways to live and work - to give them the confidence to succeed and the curiosity required to make the most of life.

© 2013-2024 Stack Abuse. All rights reserved.

AboutDisclosurePrivacyTerms