DEV Community

loading...
Cover image for Nestjs🐺⚡ | The framework of Nodejs (Part-1) | Controllers, ExceptionFilters, Providers

Nestjs🐺⚡ | The framework of Nodejs (Part-1) | Controllers, ExceptionFilters, Providers

KR Tirtho
A boy with million dreams.
Updated on ・8 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: https://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 https://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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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`
  }
}
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

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{}
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

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;
            }
        }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

create a controller:

$ nest g controller hello
Enter fullscreen mode Exit fullscreen mode

create a provider:

$ nest g provider hello
Enter fullscreen mode Exit fullscreen mode

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

Discussion (12)

Collapse
buphmin profile image
buphmin

Also fun fact* NestJS with the fastify adapter is extremely fast! In my testing it was faster than express, koa, sails, and adonis.

We use NestJS at my work for a specific project and we have been quite satisfied. We made our decision based on it's flexibility combined with out of the box functionality which was perfect to get more junior developers building something fairly well structured without much oversight.

*testing on a local 1G ethernet network using one computer as a server and another as the stress tester

Collapse
markpieszak profile image
Mark Pieszak

That's great to hear! Do you have any applications in production from your organization? I'd love to take a look and share it with the Nest team! 🐈

Collapse
buphmin profile image
buphmin

Unfortunately we are still in stealth mode, but perhaps soon! We are using NestJS in production to good effect under relatively low load with closed beta users. Obviously a lot goes into response time, not just the framework, but we are routinely able to achieve sub 100ms API requests after the JIT has optimized the calls. Our frontend devs thought they were still using inline JS mock data because the response time was so low :3

Collapse
zaimazhar97 profile image
zaimazhar97

A good introduction to NestJS! Thanks.

I've worked with IBM's LoopBack and able to relate some terminologies in your article but it might sounds complete gibberish to some newcomers. Overall still a good introduction. 👍🏽

Collapse
krtirtho profile image
KR Tirtho Author

Thank you very much❤️

Collapse
ninan_phillip profile image
Phillip Ninan

Great read! I'm a big fan of Angular and Spring Boot. This seems like best of both worlds.

Collapse
markpieszak profile image
Mark Pieszak

Give it a shot! You’ll love it 🐈

Collapse
akiraah profile image
akiraah • Edited

Annotations, a lot of boiler plate... Very similar to spring boot, no thanks.

Collapse
markpieszak profile image
Mark Pieszak

Wait until you see how much it boosts developer productivity! 🙌

Only shares some comparisons to Spring boot, but the difference is quick stark in most ways!

Collapse
akiraah profile image
akiraah

This defeats the purpose of using JavaScript to begin with. You don't need to use tools such a DI, DI was created especially for OOP languages. You can create a simple rest express app without these boilerplate code.

Thread Thread
markpieszak profile image
Mark Pieszak • Edited

DI helps separate a class from its dependencies.

Take Testing for example, with DI it takes 10x less effort to swap / mock / anything you need to do.
You could test things in isolation, provide alternative testing containers (with different databases), the number of benefits highly outweighs any cons. But it always depends on the team and projects, right!

As you said for a simple Rest application, you might not even need it (or Nest), but if you're dealing with a gigantic REST or GraphQL app, or monorepo with lots of apps + micro services, it becomes really advantageous!

Collapse
markpieszak profile image
Mark Pieszak

Great write up! 💯