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
. TheModuleRef
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 usepassport
to make a Authentication flow in Nestjs here. Here, thisAuthGuard
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 asgetRestrictedData
inHelloController
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
Top comments (1)
Looks really interesting. As someone with some experience with Spring Boot, using the same ideas in Node is a game changer. It provides the Flexibility of Javascript with the Spring Boot architecture and I love it.