Intro to Nestjs Framework for Reliable Server-Side Apps on Nodejs

List Of Content

ADS Area (CARBON)

ADS Area (CARBON)

 

Nest.js Introduction

Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript (preserves compatibility with pure JavaScript) and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming). (Docs)

Under the hood, Nest makes use of Express but also provides compatibility with a wide range of other libraries (e.g. Fastify). This allows for easy use of the myriad third-party plugins which are available.

What makes Nest special compared to other Nodejs server-side frameworks that it is based and inspired of the Angular architecture which allows it to implement an Angular similar Module system that allows the encapsulation and efficient data interpolation between modules.

The main aim behind building Nest is providing Enterprise Level open-source Framework architecture which allows you to build high-level, scalable and maintainable code base which you can develop you and your team with ease and less-complicated and it adds more simplicity with its decorator based architecture.

Installation & Project setup

Nestjs comes with a CLI (command line interface) which allows you to manage and create Nest projects with a minimal server application (boilerplate) that display a hello world.

Make sure to install the CLI globally.

npm install @nestjs/cli --global 

You will be able to run the CLI from anywhere on your machine.

Let's setup and create a Nest project.

nest new nest-app

The above command will ask you about a couple of information and details about the project you're creating (for ex: Project Description).

Now head to the directory of where you created the project you will find a folder with the same name as the project which has all the application files and open it up using your favorite code editor (I will be using VSCode).

Nest Modules

The first thing you will notice once you open up the generated project is that Nest uses Typescript which is one of its main strong features since Typescript gives you tons of missing features and functionalities on vanilla javascript, so if you're not familiar with Typescript I have a simple tutorial Made for Javascript Developers you can Take a look on it from Here.

Modules are the main building block of your Nest application, by default Nest comes with a main module named app.module.ts the naming convention is inspired by Angular's ecosystem as well as everything else on Nest.

The main.ts is the main entry point of your application there is the bootstrap function which basically initializes a Nest Server which is basically and Express Server instance and it listens on a specific port (3000).

If you look inside of the App Module you will find a @Module decorator if you don't know what decorators are they are simply a self-executing function that can alter or change the base definition of a function or a class (in this case the AppModule class) and decorators in the case of Nest are being used for providing Metadata and to mark any class as a Nest compatible Module that can be used on the Nest ecosystem.

Nest is built around decorators so you will be using them a lot, so I recommend taking a look at Medium Post about Decorators for a better understanding of how they work.

You also, notice the @Module decorator taking an object as a parameter which most of them does for configuring the Module class that comes underneath it there are three main attributes for a Module.

  • imports: Used to import other Modules (so they can share services and other assets and assets).
  • controllers: For registering controllers that belong to the current module (a Controller belongs to a Module won't be able to share data with other modules unless they import the module).
  • providers:  Are Injectables (like Services) which is a class that handles takes that are linked to I/O operations or Database queries which receives those tasks from the controllers. 

The AppModule has default controller and service (provider) included it can also import and use other Modules.

A small Nest application may only need one single module but it's better to use more when the server grows.

Now let's try to create a separate Module (UserModule).

To follow up the Nest's naming convention we need to create a new folder (user) that represents the environment for the user with its controllers, models, and Modules.

/* user.module.ts */
import { Module } from '@nestjs/common';

@Module({
  imports: [],
  controllers: [],
  providers: [],
})
export class UserModule implements NestModule {}

Make sure to import the User Module into the AppModule since it is the main module so Nest knows about it.

import { UserModule } from './user/user.module';

@Module({
  imports: [UserModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Routes & Controllers 

Controllers are a crucial part of the MVC Architecture which is basically a function (method) that runs when it receives a request at the route it is listening on, for Nest and with the help of decorators you will be able to define a controller class UserController and create methods for each section of the user CRUD and using the decorators for specifying the HTTP method and optionally the route it points to.

How Nest allows you to define and structure you controllers with its bound routes is pretty cool since I tried a lot of architecture and I like Nest's Controllers Architecture so far.

So create user.controller.ts and mark the UserController with the @Controller() decorator.

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

//Pass the shared controller path between all methods 
@Controller('user')
export class CatsController {

  //Pass specific path with GET METHOD to make the getUsers method handle incoming request 
  @Get('/all')
  getUsers() {
    return 'This action returns all cats';
  }
}

Mark the UserController class with @Controller decorator which takes the shared route path between all the class methods.

Each method can represent a single route on the controller with an HTTP Method decorator to specify which type of method the method does expect, optionally you can pass in a specific route path to the decorator for the current method to receive request on the controller shared path plus its custom path.

All the HTTP methods are available as decorators with the same configuration to passed in.

You can also specify the HTTP Response code that gets sent back when you return from the controller method.

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

//Pass the shared controller path between all methods 
@Controller('user')
export class CatsController {

  //Pass specific path with GET METHOD to make the getUsers method handle incoming request 
  @Get('/all')
  @HttpCode(200)
  getUsers() {
    return 'This action returns all cats';
  }
}

In this case, when we return the response string the status code will be set to 200, by default nest uses 200 Code for GET responses and 201 for POST responses.

If you worked a lot with the MVC Architecture and with server-side apps you're probably wondering why the controller method doesn't have a request, response and next object and how can I retrieve the sent data through the request.

Well, Nest relies on dependency injection through method arguments with the help of decorators so you need to specify the decorator for example for the POST Body and reference the argument name.

import { Controller, Get, HttpCode, Body, Req, Res} from '@nestjs/common';

//Pass the shared controller path between all methods 
@Controller('user')
export class CatsController {

  //Pass specific path with GET METHOD to make the getUsers method handle incoming request 
  @Get('/all')
  @HttpCode(200)
  getUsers() {
    return 'This action returns all cats';
  }
  //Register a new fake User 
  @Post('/register')
  registerUser(@Body() body, @Req() req, @Res res, @Query query) {
    console.log("URL Query Parameters ", query);
    console.log("Express Response Object: ", res);
    console.log("Express Request Object: ", req);
    console.log("User Post Body Data: ", body);
    return "Thanks for fake registration ;)";
  }
}

The order doesn't matter since you're providing the right decorators before the argument name it is completely fine and that all works behind the scenes on the core of Nest which injects dependencies depending on the used decorator.

Learn more about controllers and their decorators from Here.

Nest Providers (Services) 

Providers are known as Injectables which are basically simple typescript classes but can be injected to other classes throughout the constructor of other class behind the scenes by the framework.

Services are a special kind of providers which works directly with the controller, it helps to respect the SRP (Single Responsibility Principle) where controllers intend to delegate a single task to a service either Synchronously or Asynchronously for example to query data from database or register user, this way it helps you create a maintainable code base and readable code.

Let's create the UserService which is going to take care of storing user's data into the database.

import { Injectable } from '@nestjs/common';
import { CreateUserDTO } from './user-create.dto';

@Injectable()
export class UserService {
  getUsers(): any[] {
    return ['Alex', 'AlexDC', 'Islem'];
  }

  save(data: CreateUserDTO) {
    return 'Success';
  }
}

For the Save method it can simulate the process of saving and storing a new user to the database, we need to pass in to it the user's data which is a type of CreateUserDTO, a DTO (Data Transfer Object) is simply a class with attributes to define a specific data structure, in this case, we put the schema DTO of the creation of a user.

We will discuss what DTO is and how it gets created in the Validation Pipe section.

Now we need to inject the single shared instance of the service to the UserController class through the constructor.

import { UserService } from './user.service';

@Controller('/user')
export class UserController {
  //Typescript will save the instance to the current class context (this.userService) as private
  constructor(private readonly userService: UserService) {}
...

Then you can use the service class as any other class instance (But you don't need to initialize it or create the instance your self, everything is managed by Nest underneath the hood).

...
@Get('/name')
@HttpCode(201)
getUsername() {
  return this.userService.getUsers();
}

@Post('/register')
register(@Body() body: CreateUserDTO) {
  //Delegate the Saving task to UserSerive
  return this.userService.save(body);
}
...

Find more details about Providers and Services from Here.

Nest Middlewares

Middleware is a simple in-middle handler that takes a user request and does some checking or validation or even altering the request then passing it to the actually desired request handler.

Nest implements Middleware in a very clever way that takes the pain and agony of dealing and binding middleware on Express.

Let's say we want to log every request that we receive from the clients using a middleware, so create a new folder src/middlewares/ and name it requestLogger.middleware.ts.

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

//A Middleware is also an Injectable class 
@Injectable()
export class RequestLoggerMiddleware implements NestMiddleware {
  //you need to implement the resolve method which takes any kind of arguments and returns a func
  resolve(...args: any[]): MiddlewareFunction {
    //Return the actual middleware handler 
    return (req, res, next) => {
      console.log('Request: ', req.path);
      next();
    };
  }
}

The middleware class implements the NestMiddleware which tells it to implement and provide a resolve method with args, the resolve method can do some pre-processing but has to return a MiddlewareFunction which takes the standard request, response and next objects of Express.

Inside of the function, you can handle your request normally but don't forget to call next in order to move execution to the next handler.

Now we must to tell Nest when to run the middleware (Globally, Per Module or Per Controller) we will tell it to run the middleware when a controller receives a request (which means all methods that does belong to that controller).

So under the user.module.ts Inject the middleware.

import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { UserController } from './user.controller';
import { UserService } from './user.service';
import { RequestLoggerMiddleware } from '../middlewares/requestLogger.middleware';

@Module({
  imports: [],
  controllers: [UserController],
  providers: [UserService],
})
//Impement the NestModule interface 
export class UserModule implements NestModule {
  //Impement the configure method for configuring all middleware that belongs to UserModule
  configure(consumer: MiddlewareConsumer) {
    //Apply RequestLoggerMiddleware or multiple middleware 
    //You can also apply it per route in forRoutes by providing route path string
    //But we provide it with the UserController to run on all Routes 
    consumer.apply(RequestLoggerMiddleware).forRoutes(UserController);
  }
}

the apply method takes a list of Middleware to be applied and forRoutes takes either a list of route path strings or list of controllers or you can even supply it a config objects.

Take a look on the Middlewares Docs for more detailed information.

Nest Pipes (Validation)

Pipes are Injectable classes which basically runs before the desired method (route handler) does, it is mostly used for input transformation or request data validation (for ex: validating POST request body to match a schema).

Nest comes with two standard pipes ValidationPipe and ParseintPipe we will discover and use the Validation Pipe to do some validation on the received POST Request data, you can use it pretty much for other HTTP methods and other types of data validation not only restricted to the body.

You can also implement your own custom Pipes which can pretty much do anything concerning input manipulation and before-handler execution.

For running validation, we first need to use two packages for that class-transformer and class-validator which are supported by Nest out of the box.

npm install class-validator class-transformer --save 

Now we need to validate the registration of a new user through the UserController register method.

Validation takes place on the DTO (Data transfer object) which is more like a schema represents the request body structure, so create a new file under the same namespace of the user module name it user-create.dto.ts.

/* user-create.dto.ts */
//Class Validation Decorators 
import { IsString, IsEmail, IsEmpty, IsNotEmpty } from 'class-validator';
//Export DTO (Which has only readonly attributes)
export class CreateUserDTO {
  //Decorators for applying validation on the username attribute 
  @IsNotEmpty()
  @IsString()
  readonly username: string;

  @IsNotEmpty()
  @IsEmail()
  readonly email: string;

  @IsNotEmpty()
  @IsString()
  readonly password: string;
}

We need to use the DTO schema as our request body data type so Nest when receives the request it knows which DTO to use for validation.

/* user.controller.ts */
...
@Post('/register')
//Make sure to specify the type of request body as DTO 
register(@Body() body: CreateUserDTO) {
  return this.userService.save(body);
}
...

Now we need to enable the validation pipe and it can be used either Globally, per module or per controller method it actually depends on the type of the application you're developing but most of the cases it is used globally to run for all controllers that exist on the application.

So open main.ts and create a new ValidationPipe instance.

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

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  //Register and User Validation Pipe 
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(5000);
}
bootstrap();

Now if you try to send a request using a third-party tool like Insomnia with a missing or invalid body attribute you will get a self-explanatory response back of what is exactly the problem and the invalid parameters.

You can disable the error message and customize it through the ValidationPipe configuration.

 If you're looking for creating custom Pipes please read more about it in the Docs.

 

 

 

Share Tutorial

Made With By

Ipenywis Founder, Game/Web Developer, Love Play Games