Nestjs🐺⚑ | The framework of Nodejs (Part-1)

Nestjs🐺⚑ | The framework of Nodejs (Part-1)

Β·

10 min read

Nestjs is a server-side framework often confused with the term "Server-Side Angular"

Even though Nest follows the pattern & design principals of Google's Angular but its significantly different than Angular by design

Nestjs is a abstraction layer over traditional nodejs server-side tools & packages

So please don't compare it with http-servers such as: express, koa, fastify, hapi etc.. Nestjs actually uses express & fastify as its platform for http server

Nestjs centralizes all the needed technologies/tools to build perfect, reliable & durable enterprise servers using Nodejs. Its in the league of Django, Spring Boot, Ruby on Rails etc.. server-side frameworks

It follows micro-services architecture but can be used for monolithic servers too

Features of Nestjs: (source: docs.nestjs.com)

  • Extensible, Reliable, Versatile, Progressive framework
  • Offers clean, straight forward & understandable architecture
  • Offers out of the box:
    • Dependency Injection,
    • Routing with decorators using Controllers
    • Security with helmet, cors , node-csrf
    • Exception filters (unhandled exception layer)
    • Guards
    • Separation of logic from controllers using Providers
    • Robust Module system
    • Lifecycle events & many more
    • unit-testing & integration-testing support with jest & super-test
  • It provides/supports (through packages):
    • http-server (express/fastify)
    • GraphQL server
    • websocket server (socket.io / ws)
    • database orm (sequelize/mongoose/typeorm/knex/prism)
    • request body validation using class-validator
    • caching using cache-manager
    • task-scheduling using cron
    • task queue using bull & many more other tools

This is not a complete tutorial. If you want to learn Nestjs completely visit docs.nestjs.com. It contains complete details about Nestjs

All of the names & technical terms might feel over whelming but these are pretty easy to implement. Some of 'em take 5-10 lines of code only to implement. But each of them are equally important for a enterprise server or a server with smaller user-base. Nestjs covers the architecture & the dependencies for us

A fact, Nestjs actually helps & guides us as a new backend developer towards all important tools along being used as a tool

Nestjs also has a powerful cli, named @nestjs/cli. It helps manipulating files/modules. Its kinda like Angluar's CLI but only handles files & modules. It helps you organizing your project more efficiently

I'll be doing a 3 parts of Nestjs tutorial. In part-1 or this part, I'll cover about only Controllers, ExceptionFilters & Providers

But first lets create the project using:

$ npm install -g @nestjs/cli
$ nest new hello-world && cd hello-world
$ npm run start:dev

This will create a directory structure of following:

hello-world/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ app.controller.ts
β”‚   β”œβ”€β”€ app.controller.spec.ts
β”‚   β”œβ”€β”€ app.module.ts
β”‚   β”œβ”€β”€ app.service.ts
β”‚   β”œβ”€β”€ app.service.spec.ts
β”‚   └── main.ts
β”œβ”€β”€ test/
β”‚   β”œβ”€β”€ app.e2e-spec.ts
β”‚   └── jest.e2e.json
β”œβ”€β”€ .gitignore
β”œβ”€β”€ .eslintrc.js
β”œβ”€β”€ .prettierrc
β”œβ”€β”€ README.md
β”œβ”€β”€ nest-cli.json
β”œβ”€β”€ package.json
β”œβ”€β”€ package-lock.json
β”œβ”€β”€ tsconfig.build.json
└── tsconfig.json

Now lets create a directory hello inside src & inside hello create 4 files for this tutorial

  • hello.controller.ts
  • hello.module.ts
  • hello.service.ts
  • hello-body.dto.ts

1. Controllers

Controllers are Nest's building block. These are where one will handle incoming request. You can define the route path with http method modifiers (Get, Post, Put, Delete etc..) decorators

Controller example:

// hello.controller.ts

import {Controller, Logger, Get, NotFoundException, Param} from "@nestjs/common"

@Controller()
export class HelloController{
        /* a logger from nestjs for logging error/other info */
    logger: Logger = new Logger(HelloController.name)
    db: {id: string, message: string}[] = []; // temporary database

    @Get("hello")
    async replyHello() {
        try {
            return "Hello";
        } catch (error) {
            this.logger.error(error?.message ?? "");
            throw error;
        }
    }

    @Get("hello/:helloId") // dyanmic parameter just like express, koa-router etc...
        async replyExactHello(
           /*pass the same dynamic parameter from "hello/:helloId" in 
             @Param decorator's first to let nestjs find the parameter
             correctly
            */
           @Param("helloId") id: string
        ) {
        try {
            /*returning the correct temp hello message*/
            const message = this.db.find(hello=>hello.id===id)?.message
            if(!message) throw new NotFoundException("desired `hello` not found") //404 error
            return message;
        } catch (error) {
            /* will log the error & autmatically send the error as response with all required data */
            this.logger.error(error?.message ?? "");
            throw error;
        }
    }
}

Nestjs uses decorator pattern & its primarily written in Typescript but it supports JavaScript too. You can also perform validation on request-body using class-validator

////// main.ts //////
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./hello.module";
import { ValidationPipe } from "@nestjs/common";

async function bootstrap() {
    /* Creates a nest application for monolithic servers */
    const app = await NestFactory.create(AppModule, { logger: false });

    // validation is done through Nestjs pipes. 
    // Nestjs Pipes run before a specified request (kinda like middlewre)
    /* Enabling Validation pipe globally.
       Thoough it can be done in module level too
        */
    app.useGlobalPipes(new ValidationPipe());
    await app.listen(PORT);
}
bootstrap()

Here I'm just creating a Nestjs server instance & adding ValidatonPipe globally. Pipes are just methods that run before Controllers. Pipes can be used at method/argument level also using @UsePipes decorator. You can even create your own custom Pipes. Also you might've noticed AppModule . It's the main point of Nestjs for making all the things work. You'l find more about AppModule after Exception Filters section

Now lets create a body validation Schema with class-validator & decorators

////// hello-body.dto.ts //////
import {IsDefined, IsNotEmpty} from "class-validator"

export class HelloBodyDTO{
  @IsDefined()
  @IsNotEmpty({message: "A custom error message if you want to"})
    message!: string;
}

@IsDefined & @IsNotEmpty will validate a string which is defined & at least has a length of 1 or in other words the string shouldn't be just "" . Now lets use this one in a @Post request controller:

////// hello.controller.ts //////
import {Controller, Logger, Get, NotFoundException, Post, Body} from "@nestjs/common"
import {HelloBodyDTO} from "./hello-body.dto"
import {v4 as uuid} from "uuid"

@Controller()
export class HelloController{
  // ... previously written stuff from the `Controller` part

  // decorator name is similar to http verbs e.g. POST -> @Post
  @Post("hello")
    saveHello(
        /*Just pass the class as a type & the validation will be done automatically*/
        @Body() body: HelloBodyDTO
    ){
        try{
      const id = uuid()
            const hello = {id, message: body.message}
            this.db.push(hello) // saving in the temp db
            return hello;
        }
        catch (error){
                this.logger.error(error?.message ?? "");
        throw error;
        }
    }
}

2. Exception Filters

Exception filters are error handlers that runs when a Controller throws error. It handles that error automatically & sends appropriate, user-friendly error response

If you've followed the code you might've got a hint that Nestjs uses a HttpExceptionFilter by default (globally) . The package @nestjs/common provides many HttpException inherited Exceptions e.g. NotFoundException , BadRequestException, NotAcceptableException , UnauthorizedException and many more. You can even create your very own custom ExceptionFilter

Learn how to create a custom ExceptionFilter

If you want to use a custom exception filter in a route handler, you've to use @UseFilter decorator

// ... other stuff
import {ForbiddenException} from "@nestjs/common"
import {CustomHttpExceptionFilter} from "./custom-filter"

// a simple handler method inside a Controller
@Post("throw-error")
@UseFilters(new CustomHttpExceptionFilter())
errorThrowingHandler(){
    throw new ForbiddenException("its forbidden");
}
// ... other stuff

Using this long code @UseFilters(new HttpExceptionFilter()) before every handler/controller can't be hard but if your application has a usecase for using it globally then you just have to use useGlobalFilters of Nestjs server instance & pass all the global filters as parameters

///// main.ts /////

// ...other stuff
import {CustomHttpExceptionFilter} from "./custom-filter"

app.useGlobalFilters(new CustomHttpExceptionFilter())
// ...other stuff

3. Providers

Providers are another essential part of Nestjs. By far, I was using a temporary variable to store data. That's why such simple thing can be done through Controller handlers. But for bigger, complex logic, it'd be hard to do code separation & reuse. That's where providers comes to play....

You can declare/create a provider using the @Injectable decorator on top of a class. Then Dependency injection/Logging etc can be done through providers

Here's a provider example. I'm using a custom variable as a database for ease of understanding. But most of the time create, find, findById, delete, deleteById etc are provided by the database ORM. So in real-world scenarios these methods aren't needed to be implemented in Providers. Providers should be used for handling more complex logic. But for demonstration lets think these methods as complex logic

////// hello.service.ts ///////

import { Injectable } from "@nestjs/common"
import {v4 as uuid} from "uuid"

@Injectable()
export class HelloService{
  db: {id: string, message: string}[] = []

  async findById(id: string){
        return this.db.find(hello=>hello.id===id)
  }

  async create(message: string){
        const id = uuid()
        const hello = {id, message}
        this.db.push(hello)
        return hello;
  }

  async deleteById(id: string){
        this.db = this.db.filter(hello=>hello.id!==id)
    return `DELETED node ${id} from db`
  }
}

Now, lets convert the HelloController for using HelloService through Dependency Injection. But before we've to put HelloService inside the HelloModule

A module is a class annotated with a @Module() decorator. The @Module() decorator provides metadata that Nest makes use of to organize the application structure. Each application has at least one module, a root module. The root module is the starting point Nest uses to build the application graph - the internal data structure Nest uses to resolve module and provider relationships and dependencies

That module is the main thing that helps Nest making the dependency graph for Dependency Injection. Example of an app.module.ts :

////// app.module.ts //////
import { Module } from '@nestjs/common';
/*This is the base '/' controller */
import { AppController } from './app.controller';
/* basic provider for AppController */
import { AppService } from './app.service';

@Module({
  /*this where descendent modules get added
            we've to do this if we were importing another inside
            an other module to be able to use its providers
     */
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

We've to add every providers (Injectable)/controllers that we use inside our controller/provider in a module. Lets put HelloService & HelloController in HelloModule:

////// hello.module.ts //////
import {Module} from "@nestjs/common"
import {HelloService} from "./hello.service"
import {HelloController} from "./hello.controller"

@Module({
  /* put all providers that is under this module graph to help Nest to
         inject those in the controllers
  */
    providers: [HelloService],
  /* put controllers here for letting Nest recognize all the route/path &
     their handlers
    */
  controllers: [HelloController],
  /*put those providers which you wanna use outside of this module
    In an outside module when HelloModule gets imported
  */
  exports: []
})
export class HelloModule{}

To let Nest recognize HelloModule as module, lets add HelloModule inside the imports array of AppModule:

///// app.module.ts //////
// ... previously imported stuff
import {HelloModule} from "./hello/hello.module"

@Module({
    imports: [HelloModule],
    // ... other properties previously added
})
export class AppModule {}

And this AppModule is used as the entrypoint of NestFactory instance in main.ts

Now we can easily use HelloService inside HelloController or in other Module's controllers/providers

////// hello.controller.ts //////
// ... previously imported stuff
import {HelloService} from "./hello.service"

@Controller()
export class HelloController{

    logger: Logger = new Logger(HelloController.name)

    /* just create a contructor arg and set the type as the provider
             & that's gonna do the work
         */
    constructor(private readonly helloService: HelloService){}

    @Get("hello")
    async replyHello() {
        try {
            return "Hello";
        } catch (error) {
            this.logger.error(error?.message ?? "");
            throw error;
        }
    }

    @Get("hello/:helloId")
        async replyExactHello(@Param("helloId") id: string) {
        try {
            /*using provider's methods*/
            const message = await this.helloService.find(id)?.message;
                        if(!message) throw new NotFoundException("desired `hello` not found") //404 error
            return message;
        } catch (error) {
            this.logger.error(error?.message ?? "");
            throw error;
        }
    }


      @Post("hello")
        saveHello(@Body() body: HelloBodyDTO){
            try{
        /* creating `hello` using the provider HelloService */
                return await this.helloService.create(body.message)
            }
            catch (error){
                this.logger.error(error?.message ?? "");
        throw error;
            }
        }
}

Don't be afraid of Nestjs's Module system. Its hard for the first time but once you get the idea, it all makes sense & this module system is required for Nestjs to do all those cool Dependecy Injection.

BTW, you don't have to add providers/controllers manually in the Module. If you create modules/providers/controller using the nest-cli, it'll be done automatically. Above mentioned Module managing steps can be done automatically just by using these 3 commands

create a module:

$ nest g module hello

create a controller:

$ nest g controller hello

create a provider:

$ nest g provider hello

Please don't hurt me😢. I know, I should've shown this easier way earlier😁. But that idea of how module works in Nestjs often troubles people to not use Nestjs. So its important to getting the deep insight of it. You can take deep insight of Nestjs Module system here

Here's the complete application

Β