Руководство по Nest.js — Создание REST API с помощью Nest и Node PlatoBlockchain Data Intelligence. Вертикальный поиск. Ай.

Руководство по Nest.js — Создание REST API с помощью Nest и 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!

Исходный код: As usual, you can fork and tinker with the source code hosted on GitHub.

Примечание: Мы будем использовать Postman для тестирования API в нашей демонстрации. Вы можете скачать его на Страница загрузки почтальона. Кроме того, вы можете просто использовать браузер, командную строку curl инструмент или любой другой инструмент, с которым вы, возможно, знакомы.

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

Результаты в:

Usage: nest  [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]                          Adds support for an external library to your project.
  generate|g [options]  [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.

Теперь вы можете 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

Если вы посмотрите на package.json file, you will notice in the script segment, the value for pnpm run start is nest start:


    
"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

Ассоциация package.json file is the heart of the Node.js and by extenssion, 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 скрипты.

Ассоциация 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

Ассоциация 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 .

/src

Ассоциация 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.

Ассоциация AppModule is a class that is created as a module, using the @Module decorator. In the app.module.ts файл, AppService от ./app.service и AppController от ./app.controller are also imported.

Ассоциация AppController is also a class that is created using the @Controller decorator, while the AppService is a class that is created using the @Injectable аннотаций.

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.

Также в 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:

Руководство по Nest.js — Создание REST API с помощью Nest и Node PlatoBlockchain Data Intelligence. Вертикальный поиск. Ай.

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. Модули
  2. Контроллеры
  3. Провайдеры

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и ./app.controller files; leaving just main.ts, to emulate a from-scratch development lifecycle.

At this point, when we remove the imported AppModule файл из 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.

Модули

В 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, и запустить nest start.

Перейдите в http://localhost:3000 in your browser and you will get a 404 error:

Руководство по Nest.js — Создание REST API с помощью Nest и Node PlatoBlockchain Data Intelligence. Вертикальный поиск. Ай.

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

Еще в app.module.tsу нас есть 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.



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

export class AppModule {}

Now, we have a Nest.js module!

Примечание: A module is a class that is annotated with a @Module() декоратор.

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.

Ассоциация @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() декоратор.

В 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:



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

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

export class AppModule {}

Обработка GET-запросов

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:



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

@Controller({})

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

Это приводит к:

Руководство по Nest.js — Создание REST API с помощью Nest и Node PlatoBlockchain Data Intelligence. Вертикальный поиск. Ай.

Hmm, 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:



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

@Controller({})

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

Перейдите в http://localhost:3000 in your browser and you will see the object:

Руководство по Nest.js — Создание REST API с помощью Nest и Node PlatoBlockchain Data Intelligence. Вертикальный поиск. Ай.

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()и @Head().

Ассоциация @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() декоратор:

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.

Руководство по Nest.js — Создание REST API с помощью Nest и Node PlatoBlockchain Data Intelligence. Вертикальный поиск. Ай.

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:

Ознакомьтесь с нашим практическим руководством по изучению Git с рекомендациями, принятыми в отрасли стандартами и прилагаемой памяткой. Перестаньте гуглить команды Git и на самом деле изучить это!

Руководство по Nest.js — Создание REST API с помощью Nest и Node PlatoBlockchain Data Intelligence. Вертикальный поиск. Ай.

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;
  }
}

Ассоциация userId is returned successfully:

Руководство по Nest.js — Создание REST API с помощью Nest и Node PlatoBlockchain Data Intelligence. Вертикальный поиск. Ай.

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 {
    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 {
    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

Ассоциация @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() декоратор:

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.

Затем в user.controller.ts, we can configure all route handlers therein to be prefixed with /user/ by writing code like shown below:



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:

Руководство по Nest.js — Создание REST API с помощью Nest и Node PlatoBlockchain Data Intelligence. Вертикальный поиск. Ай.

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 {}

Затем импортируйте UserModule в 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.

Примечание: Nest makes it easy to (g)enerate (mo)dules and (co)ntrollers using the nest g mo и 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.

Провайдеры

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

Это создает UserService wherein we would define all business logic for the UserController, Так что UserController only handles requests and responses. In user.service.ts, мы видим, что @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.

Ранее в user.controller.ts, we had defined the business logic for getting the user object, but now, we should define the same in the UserService:



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

@Controller({})

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

Далее в 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:



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

Таким образом, UserController class, now depends on the UserService class in a concept known as
внедрение зависимости.

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



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


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 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.

Совет: 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:



@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);
}


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:



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


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 и в 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 в /user directory, we need to create a file /update-user.dto.ts с .dto.ts extension where we define and export the UpdateUserDto class for use in the user.service.ts и user.controller.ts файлов:



export class UpdateUserDto {
  name: string;
  email: string;
}

...
import { UpdateUserDto } from './dto/update-user.dto';

@Patch('/:userId')
update(
  @Body() updateUserDto: UpdateUserDto,
  @Param() param: { userId: number },
) {
  return this.userService.update(updateUserDto, param);
}

...
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 и 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.

Ассоциация class-validator package makes it possible to validate decorators and non-decorators, using валидатор.js internally. While the class-transformer package makes it possible to transform objects into instance 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 и class-transformer пакеты:

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

Затем перейдите к 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:



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 и email in UpdateUserDto:



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

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

  @IsEmail()
  email: string;
}

Ассоциация @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:

Руководство по Nest.js — Создание REST API с помощью Nest и Node PlatoBlockchain Data Intelligence. Вертикальный поиск. Ай.

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 и email properties, but a request also includes a country property, we can remove the country property from the resulting object by setting whitelist в true when we instantiate 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({
      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:

Руководство по Nest.js — Создание REST API с помощью Nest и Node PlatoBlockchain Data Intelligence. Вертикальный поиск. Ай.

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:


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

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


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

Ассоциация ParseIntPipe defines the built-in ParseInt Pipe and ensures that the data it is it 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:

Руководство по Nest.js — Создание REST API с помощью Nest и Node PlatoBlockchain Data Intelligence. Вертикальный поиск. Ай.

But with a numeric value, the validation passes successfully:

Руководство по Nest.js — Создание REST API с помощью Nest и Node PlatoBlockchain Data Intelligence. Вертикальный поиск. Ай.

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



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


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.

Заключение

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.

Спасибо за чтение!

Дополнительные ресурсы

Nest.js Docs
Angular Docs

Отметка времени:

Больше от Стекабьюс