Nestjs🐺⚡ | The framework of Nodejs (Part-2)

Nestjs🐺⚡ | The framework of Nodejs (Part-2)

·

9 min read

If you haven't read the part-1, please first read that else you'll feel this given information is out of context

In Part-2, I'll be discussing Nestjs Modules, Circular Dependency, Guards

1. Modules

In part-1, there was a smidge description of modules. Modules in Nestjs aren't global instead it has depth. But can be shared across any other modules too. Though it supports Global Module like Angular, it is more recommended to keep Services/Controllers in the module where they're mostly used in Nestjs

Most of the time modules will be generated through the NestCLI & providers/controllers generated in that module's context will get automatically added by the CLI. These are called feature modules

Here's a module example:

////// hi.module.ts //////
import {Module} from "@nestjs/common"
import HiService from "./hi.service";
import HiController from "hi.controller";

@Module({
  providers: [HiService],
  controllers: [HiController],
  exports: [HiService]
})
export class HiModule{
}

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

@Module({
    imports: [HiModule],
  providers: [HelloService],
  controllers: [HelloController],
  exports: [HelloService]
})
export class HelloModule{
}

The @Module decorator's controllers array-property is used for all the controllers that the module uses or all the classes decorated with the @Controller decorator. The providers property is used for service or classes that are decorated with an @Injectable decorator. Remember, anything Injectable is a provider & you've to put it in providers field to be able to inject/use it.

The exports property is used to export/expose the providers that can be shared with other modules. Put any providers that you want to inject/use in other modules

The imports property is the exact opposite of exports. To be able to use/inject any external providers in a provider/controller of another module, you've to add that exported provider's module in the imports field of another module

2. Circular Dependency

Often times you want to use a provider in another module's provider & another modules' provider in that provider/controller. In this case, it creates a circular dependency. Circular dependencies can arise in Nest between modules and between providers. One should always try best to avoid Circular Dependency in Nestjs but sometimes it's not possible. In this case, forwardRef & @Inject parameter decorator comes handy for providers which are within the same module context

Example of using forwardRef accross providers from same module to resolve circular dependency:

///// bye.service.ts /////
import { forwardRef, Inject, Injectable } from '@nestjs/common';
import { HelloService } from './hello.service';

@Injectable()
export class ByeService {
  constructor(
        // injecting HelloService
    @Inject(forwardRef(() => HelloService))
    private helloService: HelloService,
  ) {}

  getBye(arg: string) {
    return `bye bye, ${arg}`;
  }

    // it uses `helloService` & is within same module
  helloServiceUsingMethod() {
    return this.helloService.getHello('bye');
  }
}

///// hello.service.ts /////
import { forwardRef, Inject, Injectable } from '@nestjs/common';
import { ByeService } from './bye.service';

@Injectable()
export class HelloService {
    // ...other stuff

  constructor(
        // injecting ByeService
    @Inject(forwardRef(() => ByeService))
    private byeService: ByeService,
  ) {}

  getHello(arg: string) {
    return `hello for ${arg}`;
  }

  byeServiceUsingMethod() {
    return this.byeService.getBye('hello');
  }

    // ....other stuff
}

Let's add newly created ByeService in /hello module or HelloModule's providers field

////// hello.module.ts //////
// import stuff
import {ByeService} from "./bye.service"

@Module({
  providers: [HelloService, ByeService], // new bye-service added
  controllers: [HelloController],
  exports: [HelloService]
})
export class HelloModule{
}

Now, what about providers that are from the external modules? No worries, just do like above for the providers & just use forwardRef in the imports field of both modules to import each other's providers in their context

Example of forwarding ref of external providers across modules:

////// hi.module.ts //////
import { forwardRef, Module } from '@nestjs/common';
import HiService from "./hi.service";
import HiController from "hi.controller";
import HelloModule from "../hello/hello.module";

@Module({
  imports: [forwardRef(() => HelloModule)], // importing HelloMoule using forwardRef
  providers: [HiService],
  controllers: [HiController],
  exports: [HiService] // exporting hi-service for using in hello-service
})
export class HiModule{
}

////// hello.module.ts//////
import {Module, forwardRef} from "@nestjs/common"
import HelloService from "./hello.service";
import HelloController from "hello.controller";
import HiModule from "../hi/hi.module";
import ByeService from "./bye.service";

@Module({
    imports: [forwardRef(() => HiModule)],
  providers: [HelloService, ByeService],
  controllers: [HelloController],
  exports: [HelloService] // exporting hello-service for using in hi-service
})
export class HelloModule{
}

Now that both module's providers are available in each other's scope, let's use forwardRef in their providers HelloService & HiService to resolve their circular dependence:

///// hello.service.ts //////
import {Injectable, Inject, forwardRef} from "@nestjs/common"
import HiService from "../hi/hi.service"

@Injectable()
export class HelloService{
  // .... other properties/methods

    constructor(
        // just like provider-scoped circular dependency
        @Inject(forwardRef(()=>HiService))
        private hiService: HiService
     ){
    }

    getHello(arg: string){
        return `hello for ${arg}`
    }

    // a method that uses `hiService`
  hiServiceUsingMethod(){
        return this.hiService.getHi("hello");
  }
  // .... other properties/methods
}

///// hi.service.ts /////
import {Injectable, Inject, forwardRef} from "@nestjs/common"
import HelloService from "../hello/hello.service"

@Injectable()
export class HelloService{
  // .... other properties/methods

    constructor(
        @Inject(forwardRef(()=>HelloService)) private helloService: HelloService
     ){
    }

    getHi(arg: string){
        return `hi for ${arg}`
    }

    // a method that uses `helloService`
  helloServiceUsingMethod(){
        return this.helloService.getHello("hi");
  }
  // .... other properties/methods
}

Make sure your code does not depend on which constructor is called first as the order of instantiation of these provider classes are indeterminate

There's an advanced alternative to forwardRef. The ModuleRef class is provided from @nestjs/core for dynamically instantiating both static and scoped providers. It can be mainly used to navigate the internal list of providers and obtain a reference to any provider using its injection token as a lookup key. ModuleRef can be injected into a class in the normal way. Learn more about [ModuleRef](https://docs.nestjs.com/fundamentals/module-ref)

3. Guards

According to Nestjs docs, Guards have a single responsibility. It's their job to determine if a request will be handled by the controller or not depending on certain conditions (Specifically user-defined logic). It's useful for authentication/authorization & is the recommended way to handle authentication/authorization in Nestjs. Though authentication/permissions etc.. can be done with middleware & is done in express or other HTTP servers as these don't have connected strong context & need no reason to know about which method will be used to handle the request. Middlewares only have the next function, nothing else thus it's kinda dumb for Nestjs. But Guards have access to the execution context. It's designed more like exception filters, pipes & interceptors.

Guards are executed after each middleware but before any interceptor or pipe

Guards are a kind of provider as its class also needs to be annotated with @Injectable decorator but it has to implement interface CanActivate or provide the method canActivate in case of JS

Most of the time authentication will be handled using passport or similar libraries. And Nestjs docs has an explanation of how to use passport to make a Authentication flow in Nestjs here. Here, this AuthGuard is usable but not production safe. Just using it for demonstration purpose

Example of an AuthGaurd:

////// auth.guard.ts /////

import { Injectable, CanActivate, ExecutionContext, Logger } from '@nestjs/common';
import { Observable } from 'rxjs';

function validateToken(token: string): boolean{
    return true
}

@Injectable()
export class AuthGuard implements CanActivate {
    logger: Logger = new Logger(AuthGuard.name)  

  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
        try{
          // gives access to the express/fastify request object
        const request = context.switchToHttp().getRequest();
            // jwt/any kind of token
        const token = request?.hearders?.["Authorization"]?.split(" ")[1]
            if(!token)return false; // no token no entry

          return validateToken(token)
        }
        catch(e){
            this.logger.error(e)
            return false
        }
  }
}

Just like exception-filter/pipes you can use Guards in method-scope/controller-scope using @UseGaurds() decorator. It can take any amount of Guards as its arguments

method-scoped Guard example:

////// hello.controller.ts ///////
// ... import stuff
import {UseGuards} from "@nestjs/commmon"
import {AuthGuard} from "../../guards/auth.guard"

@Controller()
export class HelloController{
  // ..... other stuff

    @Get("/restricted-data")
    @UseGuards(AuthGuard)      // or pass it already being instantated as `new AuthGuard()`                             
    async getRestrictedData(){ // if it doesn't require dependency injection
        // ... logic
        return {};
    }

    // ..... other stuff
}

BTW, you can assign new properties to the request object in the Guard & can access it through @Req() parameter-decorator in any Controller route handler such as getRestrictedData in HelloController

Just like pipes/exception-filters, you can use Guards globally with the app's useGlobalGaurds method. Then no need to use @UseGaurds() for each controller/handler that requires that Guard

Example of global guards:

///// main.ts /////
// ...import stuff
import {AuthGuard} from "./guards/auth.guard"

async function bootstrap(){
    // ...other stuff

    app.useGlobalGuards(new AuthGuard())

    // ...other stuff
}

bootstrap()

But it'll throw an error if you're using/injecting other providers inside that Guard. But if you want to keep both dependency-injection & global scope then providing it through global AppModule & then setting it as a global guard will work

DI capable Global Guard:

///// app.module.ts //////
// ...import other stuff
import {AuthGuard} from "./guards/auth.guard"

// unique key/id for selecting the gaurd from within the NestFactory instance
export const AUTH_GUARD = "unqiue-auth-guard";

@Module({
  // ...other stuff

    providers: [
        AppService,
        {provide: AUTH_GUARD, useClass: AuthGuard}
    ],

  // ...other stuff
})
export class AppModule{
}

///// main.ts /////
// ...import stuff
import {AUTH_GUARD} from "./app.module";

async function bootstrap(){
    // ...other stuff

    const authGuard = app.select(AppModule).get(AUTH_GUARD)

    app.useGlobalGuards(authGuard);

    // ...other stuff
}

bootstrap()

Now, there another problem arises. How will one make a route public/unrestricted from this Guard? This is where the Reflector comes in handy. It's a special class provided by @nestjs/core that can be accessed in any module-scoped providers/controllers or simply, in any controller/provider/guard/exception-filter/interceptor/pipe that isn't instantiated globally

With Reflector, @SetMetadata() decorator & custom-decorator we can simply handle this case

@SetMetadata() is a both method & class decorator provided by @nestjs/common & can be used to set special key-value metadata for a method/class & this can be accessed through the Reflector that is injected in every @Injectable() & @Controller() available in AppModule's context

Custom Decorator example:

///// public.decorator.ts /////

import { SetMetadata } from "@nestjs/common";

export const IS_PUBLIC_KEY = "THIS_ROUTE_IS_PUBLIC"

// decorators are functions inside function with access to extra metadata provided 
// by the JSVM (JavaScript Interpreter). So you can ovbiously call 
// a decorator in a function like normal functions but just remember to `return` it to
// let the decorator's inner function to access those metadata about the class/method/parameter/property
// its currently being applied to
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);

// the decorator flow-> `Public` is a function which returns & calls `SetMetadata`
// function which also returns & calls an inner function within it. Its called
// **function-currying**
// More on Wikipedia: https://en.wikipedia.org/wiki/Currying

Now in AuthGuard's canActivate method lets get the metadata of currently active class/method in context:

////// auth.guard.ts /////
// ...import stuff
import { Reflector } from "@nestjs/core";
import {IS_PUBLIC_KEY} from "./decorators/public.decorator"

@Injectable()
export class AuthGuard implements CanActivate {
        // ...other stuff

        // just add the Reflector as a type
    constructor(private readonly reflector: Reflector){}

  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
        try{
            // accessing the passed metadata with its unique key/id
            // within the current execution context
            const isPublic = this.reflector.getAllAndOverride<boolean>(
                        IS_PUBLIC_KEY,
                        [
              context.getHandler(),
              context.getClass(),
                  ]
                    );
            if(isPublic) return true;

          // ... other validation logic/stuff
        }
        catch(e){
            this.logger.error(e)
            return false
        }
  }
}

Now we only have the job to apply the custom @Public() method/class decorator in a route to make it unrestricted. If you've read part-1 then you know that HelloController (created in part-1) has a GET /hello route that responds with hello on request. But for the AuthGuard, that route will be restricted. But what in the world should make someone not getting a warm hello?!. So let's make it open to everyone:

////// hello.controller.ts ///////
// ... import stuff
import {Public} from "../decorators/public.decorator"

@Controller()
export class HelloController{
  // ..... other stuff

    @Get("hello")
  @Public() // now everyone gets a hello ;)
    async replyHello(){
        // ... logic
    }

    // ..... other stuff
}

Here's the complete application with today's update

After update, All the routes except /hello will return

{"statusCode": 403,"message": "Forbidden resource", "error": "Forbidden"}

Providing any jwt-token in this Bearer <token> format with request-header's Authorization field will make the protected routes work for now