Skip to content

Latest commit

 

History

History
3273 lines (2637 loc) · 101 KB

File metadata and controls

3273 lines (2637 loc) · 101 KB

Chapter-6 Other Building Blocks By Example

Introducing More Building Blocks

chapter 6 1
Introducing More Building Blocks

As we’ve seen throughout the course so far. In NestJS - applications everything has its place.

chapter 6 2
Introducing More Building Blocks - 2

This "structured - application" organization, helps us mange complexity and develop with SOLID principles.

This Type of organization is, especially useful as the size of our application or team grows. Keeping code organized, establishing clear boundaries, and having dedicated architectural building blocks, all with the separate of responsibilities. Help us make sure that our application remain easily maintainable and scalable over time.

chapter 6 3
Introducing More Building Blocks - 3

In NestJS we have 4 additional "building blocks" for features, that we haven’t showcased yet!. These are:

Exception Filters

chapter 6 4
Introducing More Building Blocks - Exception Filters

Exception Filters are responsible for handling and processing "unhandled - exception" that might occur in our application. They let us control the "Exact - flow" and "Content - flow" of any or specific Responses, we send back to the client.

Pipes

chapter 6 5
Introducing More Building Blocks - Pipe

Pipes are typically useful to handle 2 - things:

  • Transition, meaning to transform "input - data" to "desired output", and

  • Validation, meaning to "evaluate input data" and if VALID - let it pass through the "Pipe" unchanged. But if "NOT - VALID", throwing an Exception.

Guards

chapter 6 6
Introducing More Building Blocks - Guards

Guards determine whether a given Request meets certain condition, like "authentication", "authorization", "roles", "ACLs[1]", etc. And if the conditions are met, the requests will be allowed to access the route.

Interceptor

chapter 6 7
Introducing More Building Blocks - Interceptor

Interceptor have many useful capabilities inspired by the "Aspect Oriented Programming[2] - technique". Interceptors make it possible to:

  1. Bind extra logic, before or after method execution.

  2. Transform the result returned from a method.

  3. Extend "basic - method" behavior.

  4. Completely "override a method", depending on specific conditions. For example: handling something like "caching - responses".

So now that we’ve covered the basics. Let’s dive into all 4 these new building blocks in the next few lessons.

Understanding Building Techniques

chapter 6 8
Understanding Building Techniques

Before we jump into the specifics of each Nest "building - block". Let’s take a step back and talk about a few approaches we can take to bind any of these "building - blocks", to different parts of our application.

chapter 6 9
Understanding Building Techniques - 2

Basically there are 3 different ways of binding to our "route - handlers" with a bonus "4th" way that specific to (Pipes).

  • Filters

  • Guards

  • Interceptors

  • Pipes

Nest "building - blocks" can be:

  • Globally - scoped

  • Controller - scoped

  • Method - scoped

  • Param - scope, which said is available to Pipes only.

Note
These different "binding - techniques" give you granularity and control at different levels in you application.

Each one does NOT override another, but rather "layers each one - "on top".

So be careful on how you implement these.

For example, if you have a globally-"scoped - Pipe", it will be applied as well as any other (Pipe) you might add. Whether it’s "controlled - scoped", "method - scoped", etc.

So far in this course we’ve already seen globally-"scoped - pipes" in action, when we use the "ValidationPipe" to helps us validate incoming - "request - payloads", amongst other things.

If we open up our "main.ts" - file.

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

async function bootstrap() {
    const app = await NestFactory.create(AppModule);

    app.useGlobalPipes(         // <<<
        new ValidationPipe({
            whitelist: true,
            transform: true,
            forbidNonWhitelisted: true,
            transformOptions: {
                enableImplicitConversion: true,
            },
        }),
    );
    await app.listen(3002);

    // console.log("app is run on port: 3002");
}
bootstrap();

We’ll see that we previously bound the "ValidationPipe" globally by calling "useGlobalPipes()" - method of our ""app" - instance".

chapter 6 10
Understanding Building Techniques - intellisence

You could see that if we type app.use "intellisence" shows us corresponding methods for every other "building - block" available here. Respectively "useGlobalPipes()", "useGlobalGuard()", "useGlobalInterceptors()", and "useGlobalFilters()", etc..

Going back to our "ValidationPipe()" here. One big limitation of setting it up and instantiating it by ourselves like this, is that we can NOT - "inject any dependencies" here!. Since we’re setting it up outside of context of any "NestJS - Module".

So how do we work around this?.

One option we have, is to set up a "Pipe" directly from inside a "Nest - Module" using the "custom - provider" based syntax, we saw in earlier lessons.

Let’s open up our "AppModule" - file, and define something called the "APP_PIPE" - Provider.

// app.module.ts
import { Module, ValidationPipe } from "@nestjs/common";
import { APP_PIPE } from "@nestjs/core";
...
...

@Module({
    imports: [
        ...
        ...
    ],
    controllers: [AppController],
    providers: [
        AppService,
        {
            provide: APP_PIPE,          // <<<
            useClass: ValidationPipe,   // <<<<
        },
    ],
})
export class AppModule {}

This "APP_PIPE" - "provider" is a "special - TOKEN" exported from "@nestjs/core" - packages.

Providing - "ValidationPipe" in this manner. Let’s Nest instantiate the "ValidationPipe" within the scope of the "AppModule" and once created, registers it as a "Global Pipe".

Note that there are also "corresponding - tokens" for every other "building - block" feature!, such as "APP_INTERCEPTOR", "APP_GUARD", and "APP_FILTER".

Back to our "ValidationPipe". What if we don’t want to use it globally? But some are more specific like on a "certain - Controller".

Let’s imagine that we want to bind a "ValidationPipe" to every - "route handler" defined only within our "CoffeesController".

Let’s open up our "CoffeesController" - file and make use a new decorator "@UsePipes()" that we haven’t seen yet.

// coffees.controller.ts
import { Controller, Get, Param, Body, Post, Patch, Delete, Query, Inject, UsePipes, ValidationPipe } from "@nestjs/common";

@UsePipes(ValidationPipe)           // <<<
@Controller("coffees")
export class CoffeesController {
    constructor(
        private readonly coffeesService: CoffeesService,
        @Inject(REQUEST)
        private readonly request: Request,
    ) {
        console.log("[!!] CoffeesController created");
    }
    ...
    ...
}

This @UsePipes() decorator can be passed in a "single - Pipe Class" or a "comma separated list of Pipe - Classes". Just like in other scenarios.

There are also "corresponding - decorators" for every other "building - block" that can be used here as well. Named "@UseInterceptors()", "@UseGuards()", and "@UseFilters()".

// coffees.controller.ts
import { Controller, Get, Param, Body, Post, Patch, Delete, Query, Inject, UsePipes, ValidationPipe } from "@nestjs/common";

@UsePipes(new ValidationPipe())     // <<<
@Controller("coffees")
export class CoffeesController {
    constructor(
        private readonly coffeesService: CoffeesService,
        @Inject(REQUEST)
        private readonly request: Request,
    ) {
        console.log("[!!] CoffeesController created");
    }
    ...
    ...
}

Alternatively, you can even pass an "instance" of class here. Take for example providing "new ValidationPipe()" inside of the decorator.

This is super useful when you want to pass in a specific - "configuration object" to the "ValidationPipe" for this exact scenario.

Note
As the best practice, try to apply "filters' by using "classes" instead of "instances" whenever possible.

This best practice "reduces memory usage" since Nest can easily reuse instances of the "same class", across your entire Module.

All "building - blocks" can also be "Method - scoped". Imagine that you want to bind a "Pipe" to a "specific - Route". We can achieve this by simply applying the same decorator we just saw "@UsePipes()", but on top of the specific method we want to declare it on.

Let’s say we want to add "specific validation" to our GET - findALL() - method, within "CoffeesController".

// coffees.controller.ts
import { Controller, Get, Param, Body, Post, Patch, Delete, Query, Inject, UsePipes, ValidationPipe } from "@nestjs/common";

@Controller("coffees")
export class CoffeesController {
    constructor(
        private readonly coffeesService: CoffeesService,
        @Inject(REQUEST)
        private readonly request: Request,
    ) {
        console.log("[!!] CoffeesController created");
    }

    @UsePipes(new ValidationPipe())     // <<<
    @Get()
    findAll(@Query() paginationQuery: PaginationQueryDto) {
        // const { limit, offset } = paginationQuery;
        return this.coffeesService.findAll(paginationQuery);
    }
    ...
    ...
}

With this setup, This "ValidationPipe" is only applied to this single findALl() - "Route - handler".

We are already familiar with the 3 different ways of tying "filters", "guards", "pipes", and "Interceptors" to our "Route - handlers". But as we said, there is a "4th" bonus way - that’s only available to "Pipes", and it’s called "Param-based scope".

"Param-scoped Pipes", are useful when the "validation - logic" concern ONLY ONE "specific parameter".

Let’s scroll down to the update() - method.

// coffees.controller.ts
import { Controller, Get, Param, Body, Post, Patch, Delete, Query, Inject, UsePipes, ValidationPipe } from "@nestjs/common";

@Controller("coffees")
export class CoffeesController {
    constructor(
        private readonly coffeesService: CoffeesService,
        @Inject(REQUEST)
        private readonly request: Request,
    ) {
        console.log("[!!] CoffeesController created");
    }

    @UsePipes(new ValidationPipe())     // <<<
    @Get()
    findAll(@Query() paginationQuery: PaginationQueryDto) {
        // const { limit, offset } = paginationQuery;
        return this.coffeesService.findAll(paginationQuery);
    }
    ...
    ...
    @Patch(":id")
    update(@Param("id") id: string, @Body(ValidationPipe) updateCoffeeDto: UpdateCoffeeDto) {     // <<<
        return this.coffeesService.update(id, updateCoffeeDto);
    }
}

This method takes "2 - arguments": the "resource - id", as well as the "payload" required to update the existing entity.

What if we want to bind a "Pipe" to the "body" of the request but not the "id - parameter". This is exactly where the "param - based" - Pipe comes in handy.

By passing the "ValidationPipe" - class reference, directly to the `"@Body" - decorator here, we can let Nest know to run this particular - Pipe -exclusively for just this specific parameter!, and there we have it.

With these 4 powerful "building - blocks". We can now control the "flow", "content", "validation" on anything in our application, globally, all the way down to a specific "controller", "method", or even a "parameter".

Catch Exception With Filters

chapter 6 11
Catch Exception With Filters

NestJS comes with a built-in Exception "layer", responsible for processing all "unhandled - Exceptions" across our application. When Expection is NOT handled by our application, it is automatically caught by this "layer*, which send the appropriate user-friendly response.

Out of the box. This action is performed by a built-in "global - ExceptionFilter". While this base built-in "ExceptionFilter" can automatically handle many use cases for us. We may want "full control" over it.

chapter 6 12
Catch Exception With Filters - 2

For example, we may want to add exception "logging" or "return" our Errors back in a different "JSON - schema". "Exception - Filters" are designed for exactly this purpose!.

They let us be in charge of the exact "flow of control" and the "content" of the Response being sent back to the client.

Let’s create an "ExceptionFilter", that is responsible for "catching exception" that are an instance of the "HttpException" - Class, and implement our own custom "response - logic" for it.

Let’s get started by firing up our terminal, and generating a "filter" - class using the NEST - CLI "filter - schematic", by entering:

$ nest g filter common/filters/http-exception
CREATE src/common/filters/http-exception.filter.spec.ts (201 bytes)
CREATE src/common/filters/http-exception.filter.ts (195 bytes)

Note that we generated this "filter" in a "/common/" - directory, where we can keep things that are not tied to any specific domain.

Let’s open up he newly generated "HttpExceptionFilter", and see what we have inside.

// http-exception-filter.ts
import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from '@nestjs/common';

@Catch(HttpException) // <<<
export class HttpExceptionFilter<T> implements ExceptionFilter {
  catch(exception: T, host: ArgumentsHost) {}
}

As you can see, the NEST - CLI generated and example "filter", without any "business logic" of course.

The "@Catch()" - decorator on top, binds the "required metadata" to the "ExceptionFilter". This "@Catch()" - decorator can take a "single - parameter" or a "comma separated list".

This allows us to set up a "filter" for several "types of exceptions" at once if we want it.

Since we want to process all exceptions that are instances of "HttpException". Let’s pass the "HttpException" - class between the parentheses "()".

All "ExceptionFilter’s" should implement the "ExceptionFilter[3]" - interface exported from @nestjs/common. This interface requires that we provide the "catch()" - method with its indicated "method signature". Also we can see that our class accepts a "Type - Argument", which indicates the Type of the "exception argument" in our "catch()" - method.

Again, since we want to process all exceptions that are instances of a "HttpException". Let’s changes this to "<T extends HttpException>".

// http-exception-filter.ts
import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from "@nestjs/common";
import { Response } from "express";

@Catch(HttpException)
export class HttpExceptionFilter<T extends HttpException> implements ExceptionFilter {  // <<<
    catch(exception: T, host: ArgumentsHost) {
        const ctx = host.switchToHttp();
        const response = ctx.getResponse<Response>();

        const status = exception.getStatus();
        const exceptionResponse = exception.getResponse();

        const error = typeof response === "string" ? { message: exceptionResponse } : (exceptionResponse as object);

        response.status(status).json({
            ...error
        })
    }
}

All right, with all of that setup now, we can implement our custom - "response - logic". To do this, we’ll need to access the "underlying - platform" - "Response{}" Object so that we can manipulate or transform it and CONTINUE sending the response - afterwards.

So where we get a hold of the "original Response"?

Let’s use the second parameter here, "host:" which we can see as an instance of "ArgumentsHost", and call the method "switchToHttp()" on it. Saving this as the variable "ctx", short for "context". This "switchToHttp()" - method gives us access to the native in-flight "Request or Response Objects". Which is exactly what we need here.

Next, let use this "ctx" - variable, and call the "getResponse()" - method on it. This method will return our "underlying platforms" - Response.

Note
Remember in NestJS, this is ExpressJS by default, but could also be swapped for Fastify.

For better "Type - safety" here. Let’s specify the "Type" as a "<Response>", importing this Type from the "express" - packages.

Now we have our "Response". Let’s use the "exception - parameter" available to us in this - method, and extract "2 - things". The "statusCode", and "body" from the "current - exception".

To get the "status", we can simply call the "getStatus()" - method as we see above.

Let’s also get a hold of a "raw - "exceptionResponse" ", by calling to "getResponse()" - method and saving that variable as well.

Since for demonstration purposes - we’re trying to pass back this *original - "Error - Response", we need to do a little bit of work here.

First we need to test whether the Response is a String or an Object. If it’s a string, we’re going to create an Object an put that String inside of the "message - property". Otherwise we’re all set in our "exceptionResponse" is already an Object.

By doing of this. Our Errors will now be fairly "uniform", and we can ("…​") spread this "error - variable" into our "final - response", which we’ll do in a moment!.

Great, so now that we have everything we need. Let’s start building our "response" back that we’ll be sending.

First, let’s set the "StatusCode" for the response we’re going to send back via the "status()" - method ("response.status(status)").

Lastly, we need to send the "exceptionResponse" back. Our application underlying platform is ExpressJS, which is the default. So there are several ways we could do this!.

In our case. Let’s just use Express’s ".json()" - method. We ca simply chain this method after our "status()" - call (as we see above), and using the ("…​") "spread - operator", we can pass the original Error from our Exception inside this ".json()" - method.

As of right now. Our "ExceptionFilter" here isn’t really doing anything unique yet. So let’s pass in something custom here along with the "original - exception". This way we have something we can look for in all of our "errors" to make sure everything with the "ExceptionFilter" works!.

Let’s add a new "timestamp" - property and give it the value of "new Date().toISOString()".

Great. Now with all this in place, let’s bind this "global - "ExceptionFilter" " to our application.

Since we don’t need any "external - providers" here, we can just bind this "ExceptionFilter - globally" using the " "app" - instance" in our "main.ts" - file.

Let’s head over to the "main.ts" - file and add it real quick with "app.useGlobalFilters()".

// main.ts
import { NestFactory } from "@nestjs/core";
import { HttpException, ValidationPipe } from "@nestjs/common";

import { AppModule } from "./app.module";
import { HttpExceptionFilter } from  "./common/filters/http-exception.filter";

async function bootstrap() {
    const app = await NestFactory.create(AppModule);

    app.useGlobalPipes(
        new ValidationPipe({
            whitelist: true,
            transform: true,
            forbidNonWhitelisted: true,
            transformOptions: {
                enableImplicitConversion: true,
            },
        }),
    );
    app.useGlobalFilters(new HttpExceptionFilter()); // <<<
    await app.listen(3002);

    // console.log("app is run on port: 3002");
}
bootstrap();

Now that everything’s in place. Let’s test it out by triggering some "AP errors!". Let’s make sure that our app is running in our terminal, and if not, make sure we run: "npm run start:dev".

With our applications running. Let’s open up insomnia, and perform a GET - request to a "non existing* - resource and purposely make an Error happen!.

Let’s hit someting /coffees/-1 where "-1" is obviously an "id" we don’t have in the database.

// request: 'GET - http://localhost:3002/coffees/-1'

// Body - raw: JSON
{}

// response, 404 -  NOT FOUND
{
    "statusCode": 404,
    "message": "Coffee with 'id: #-1' not found",
    "error": "Not Found",
    "timestamp": "2021-04-06T07:30:31.156Z"
}

As we can see, the Response came back with an Error and clearly used new custom "ExceptionFilter". Since the response contains our new "timestamp" - property. Perfect!.

So naturally this exception we created was a basic example implementation. But you can see that within this "ExceptionFilter", we could have just as easily used some sort of "logging - Service" to track our errors, maybe even called an "Analytics - API". Anything we’d want to do whenever an "HttpException" occurs in our application.

Protect Routes With Guards

chapter 6 13
Protect Routes With Guards

Guards have a single responsibility. Which is to determine whether a "given request" is allowed access to something.

If the "request" meets certain conditions, such as "permissions". "roles", "ACLs.[1]", etc.. It will be allowed access to that route.

If the condition are NOT met, that it will be denied and an Error will be thrown.

One of the best use-cases for "Guard": is "Authentication" and "Authorization".

chapter 6 14
Protect Routes With Guards - 2

For example, we could implement a "Guard" that "extract" and "validates a Token", and uses the extracted information to determine whether the "request can proceed or not".

Since there are many different approaches and strategies to handle authentication and authorization. In this lessons we’ll focus on a simplified example and learn how to leverage Guards themselves in our projects.

Note
If you’re interested in learning more about Authentication itself, check out our separate Course extension, which particularly focused on implementation of an Enterprise-grade Authentication feature, and all the complexities that go along with that.

All right. So to learn how to Guard - "work conceptually", let’s create a Guard that is responsible for "2" things:

  1. Validating whether an API_KEY is present within an "authorization" - Header.

  2. Validating whether the route being accessed is specified as "public".

Let’s call this new - Guard "ApiKeyGuard". Let’s fire up the terminal and generate a "Guard - class" using the Nest - CLI.

$ nest g guard common/guard/api-key
CREATE src/common/guard/api-key.guard.spec.ts (169 bytes)
CREATE src/common/guard/api-key.guard.ts (301 bytes)
Note
We generate this Guard in the "/common/" - directory, where we can keep things that aren’t tied to any specific domain.

All right, let’s open up this newly generated "ApiKeyGuard" - file, and see what we have inside.

// api-key.guard.ts
import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common";
import { Observable } from "rxjs";

@Injectable()
export class ApiKeyGuard implements CanActivate {
    canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
        return true;
    }
}

As we can see the Nest - CLI generated an example Guard for us, without any logic inside of course. Similar to Providers. A Guard is just a Class with the "@Injectable()" - decorator which we’ve seen in previous lessons.

One important requirement of Guard is, that they should implement the "canActivate" - interface exported from @nestjs/common. This interface requires us to provide the "canActivate()" - method within our class.

This "canActivate()" - method should return Boolean, indicating whether the "current - request" is allowed to proceed OR denied access. This method can also "return a Response" that’s either synchronous or asynchronous, such as a "Promise" or "Observable".

Nest will use the "return - value" to control the next action. If it return - "true": the request will be processed. If it return "false": Nest will deny the request.

Looking at the example code, the Nest - CLI generated for us here. We have "return true" hard-coded for now. This means that currently, every request will be allowed to proceed!.

Just for testing purposes, let’s changes a couple line,

// api-key.guard.ts
import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common";
import { Observable } from "rxjs";

@Injectable()
export class ApiKeyGuard implements CanActivate {
    canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
        return false;   // <<<
    }
}

We change "return true" to "return false", indicating that every request should be "denied - access".

Now that our initial "Mock Guard" is all set. Let’s bind it to our application "globally".

Let’s open up the "main.ts" - file, and add "AppUseGlobalGuards()", passing in our new "ApiKeyGuard" inside of it.

// main.ts
import { NestFactory } from "@nestjs/core";
import { HttpException, ValidationPipe } from "@nestjs/common";

import { AppModule } from "./app.module";
import { ApiKeyGuard } from  "./common/guard/api-key.guard";    // <<<

async function bootstrap() {
    const app = await NestFactory.create(AppModule);

    app.useGlobalPipes(
        new ValidationPipe({
            whitelist: true,
            transform: true,
            forbidNonWhitelisted: true,
            transformOptions: {
                enableImplicitConversion: true,
            },
        }),
    );
    app.useGlobalGuard( new ApiKeyGuard());     // <<<
    await app.listen(3002);

    // console.log("app is run on port: 3002");
}
bootstrap();

Let’s make sure that our applications is running in the background, and let’s navigate to insomnia and test any endpoint.

// request: 'GET - http://localhost:3002/coffees/'

// Body - raw: JSON
{}

// response, 403 -  FORBIDDEN
{
    "statusCode": 403,
    "message": "Forbidden resource",
    "error": "Forbidden"
}

As we can see ALL of our endpoints now responds with status "403 Forbidden Resource", just as we expected. It works perfectly.

But right now our Guard is not programmatically determining anything about our "Route" or the "Caller" yet. It’s simply always returning "false".

That doesn’t make much sense right? Instead let’s set up our Guard to handle the scenario we talked about in the beginning of this lesson. Which is to "validate an API_KEY that should be present within each request", but-only-on routes that are NOT specified as "public".

So how can we get started here?.

Well, first let’s define this "API_KEY" that we’re talking about. To make sure that we never push this secret key to our Git - Repo. Let’s define the "API_KEY" as an "environment - variable".

Open up our ".env" - file that we created in a previous lesson and let’s add the following "API_KEY" - line.

// .env
/*
* CAUTION:  never SUBMIT or PUSH this crendential '.env' - file in github or track on Git!.
* This only for course and education purpose.
*/

...
...
API_KEY=7AddwM8892Pbsewqaxx00wqaMMzal
Note
The "API_KEY" here is just a random generated String, so feel free to use whatever you’d like for this example.

Within this in place. Let’s head back to our Guard.

Here in our Guard. We want to retrieve the API_KEY from any "incoming - request" that is not labeled as "public". We’ll be handling this "public - part" in a moment.

// api-key.guard.ts
import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common";
import { Observable } from "rxjs";

@Injectable()
export class ApiKeyGuard implements CanActivate {
    canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
        const request = context.switchToHttp().getResponse<Request>()
        const authHeader = request.header("Authorization")

        return authHeader === proceess.env.API_KEY;
    }
}

For our "API_KEY". Let’s assume that the "caller" is passing this "key" as an "authorization - header". To get the information about this "HTTP - request". We’ll need to access it all from the "ExecutionContext" ("contex' - param), which inherits from "ArgumentsHost", which we’ve already familiarized yourself with when we used it with the "ExceptionFilter".

We can actually use those same "helper - methods" from before, but this time, to get the reference of the "Request - Object" instead of the Response.

This "switchToHttp" - method, guves us access to the native "in-flight Request", "Response", and "Next" Objects! Which is exactly what we need.

Next we need to call the "getRequest()" - method on it, which will return our underlying platform’s "Request wrapper object".

Remember in Nest this is ExpressJS by default. But you could also be swapped for Fastify.

For better "Type - safety" here. Let’s specify type as a <Request> importing this Type from the express - package again.

Now let’s use this "Request - object" to retrieve the "authorization - header" from each request, if it’s even there.

Lastly, let’s compare the "authorization header" passed in, with the "registered API_KEY" we have stored in our "environment - variable".

For now we’ll simply access the "environment - variables" using the "process.env" - Object but ideally you’d want to leverage the (@nestjs/config) "ConfigService" instead.

With all of this in place. Let’s open insomnia and test any endpoint in our application, and see if our Guard works so far.

// request: 'GET - http://localhost:3002/coffees/'

// Body - raw: JSON
{}

// response, 403 -  FORBIDDEN
{
    "statusCode": 403,
    "message": "Forbidden resource",
    "error": "Forbidden"
}

As we can see our application responded with the status "403 Forbidden Resource".

Since we didn’t pass in any authorization header especially one with our specific "API_KEY". It looks like our Guard is working perfectly.

Let’s try the same API - request again but this time let’s add an "authorization - header" with the "correct - API_KEY".

With these in place. Let’s hit send and call the endpoint again.

// request: 'GET - http://localhost:3002/coffees/'

// Body - raw: JSON
{}

// Header
{
    "Authorization": "7AddwM8892Pbsewqaxx00wqaMMzal"
}

// response, 200 -  OK
[
    {
        "id": 1,
        "title": "Salemba Roast#1",
        "description": null,
        "brand": "Salemba Brew",
        "recomendations": 0,
        "flavors": [
            {
                "id": 1,
                "name": "chocolate"
            },
            {
                "id": 2,
                "name": "vanilla"
            }
        ]
    },
    {
        "id": 2,
        "title": "Salemba Roast#2",
        "description": null,
        "brand": "Salemba Brew",
        "recomendations": 0,
        "flavors": []
    },
    {
        "id": 3,
        "title": "Salemba Roast#3",
        "description": null,
        "brand": "Salemba Brew",
        "recomendations": 0,
        "flavors": []
    }
]

Perfect. We’ve got a "200" response back and no error this time!.

Our Guard was able to verify that the "Authorization - header" matched our "secret - API_KEY" and we were allowed access to continue the "API - Request".

So far we’ve finished setting up the "API_KEY - validation" - functionality we wanted, but still aren’t checking whether the "specific - route" being accessed is "public" or not.

In the next chapter we’ll look at how Metadata and a few Nest features can help us achieve just that!.

Using Metadata To Build Generic Guard and Interceptor

chapter 6 15
Using Metadata To Build Generic Guard and Interceptor

In the last lesson, we implement the first goal for our "new Guard". Which was to verify an "API - Token" is present when a "Route is accessed".

In this lesson, we’ll be looking at how we can complete the next piece of functionality we needed, which was to detect whether the route being access is declared "public" or not.

chapter 6 14
Using Metadata To Build Generic Guard and Interceptor -2

How can we declaratively specific which endpoints in our application are "public", or any "data" we want stored alongside controllers or routes.

This is exactly where "custom - Metadata" comes into play.

chapter 6 16
Using Metadata To Build Generic Guard and Interceptor -3

Nest provides the ability to attach "custom - Metadata" to "routes - handlers" through the "@SetMetadata()" - decorator. The "@SetMetadata()" - decorator takes "two - parameters".

  • First being the "key" that will be used as the "lookup key",

  • Second, is "metadata - value" which can be any Type. This is where we put whatever values we want to store for this "specific - key".

So let’s put this to use, to learn how it all works.

Let’s open our "CoffeesController" - file. Head over to our "findAll()" - method, and add this @SetMetadata() - decorator on top. Making sure it’s imported from the nestjs/common package.

// coffees.controller.ts
import {
    Controller,
    Get,
    Param,
    Body,
    Post,
    Patch,
    Delete,
    Query,
    Inject,
    UsePipes,
    ValidationPipe,
    SetMetadata                     // <<<
} from "@nestjs/common";
...
...

@Controller("coffees")
export class CoffeesController {
    constructor(
        ...
        ...
    ) {
        console.log("[!!] CoffeesController created");
    }

    @SetMetadata("isPublic", true)  // <<<
    @Get()
    findAll(@Query() paginationQuery: PaginationQueryDto) {
        // const { limit, offset } = paginationQuery;
        return this.coffeesService.findAll(paginationQuery);
    }
    ...
    ...
}

Inside the decorator. Let’s pass those "Metadata - Key" and "values - parameters".

For our "key", let’s enter the String of "isPublic", for our "value" let’s enter a Boolean of "true".

This is the most bare-bones way of setting up Metadata on a route, but it’s NOT actually the "best practice".

Ideally we should create our own decorator to achieve the same result. This is much better practice, because we will have less duplicated code, we can reuse the decorator in multiple places, and a "custom - decorator" gives us much more "Type - safety".

Let’s improve our existing code and make our "own - decorator", and call it "Public".

First, let’s create a new folder within the "/common/" - directory and call it "/decorators/". Here we can store any other future decorator we might make. In this folder. Let’s create a new file called "public.decorator.ts".

Let’s open up our new file,

// public.decorator.ts
import { SetMetadata } from "@nestjs/common";

export const IS_PUBLIC_KEY ="isPublic";

export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);

In this file. We’re going to export 2 things.

  • First, our metadata "key",

  • Second, our new decorator itself, that we’re going to call "Public".

For our "key" let’s exports "const = IS_PUBLIC_KEY" and set it equal to the string of "isPublic". Just like we called previously.

The benefit of exporting this variable here is, that anywhere we might look up this metadata, we can now import this variable, instead of using "magic - string" and accidentally mistyping the name.

Now let’s export our "decorator" by typing "export const Public", and setting this equal to an arrow function ("⇒") that return "SetMetadata".

Inside of "SetMetadata()", just like before, we need to pass in "key" and "value". So let’s use our "IS_PUBLIC_KEY" - variable and for the value let’s pass in the Boolean of "true", just like we did previously.

That’s it!. We just made our first - decorator!.

Next let’s swap out the code we previously added in our "CoffeesController" to use our "new - decorator". Let’s head back over to that findAll() - method signature and replace the previously added @SetMetadata - expression with this @Public() - decorator, making sure import it from our local directory.

// coffees.controller.ts
...
...

import { Public } from "../common/decorators/public.decorator";

@Controller("coffees")
export class CoffeesController {
    constructor(
        ...
        ...
    ) {
        console.log("[!!] CoffeesController created");
    }

    @Public()
    @Get()
    findAll(@Query() paginationQuery: PaginationQueryDto) {
        // const { limit, offset } = paginationQuery;
        return this.coffeesService.findAll(paginationQuery);
    }
    ...
    ...
}

Perfect. We now have much more "future - proof" and easily reusable decorator that we can use throughout entire application if needed!.

Let’s tie everything together and fix up that "ApiKeyGuard" to use it.

Currently, our Guard return "true" or "false", depending on whether the "API_KEY" was provided with the request. But now we need to add our "isPublic" - logic here.

We need the Guard to return "true", when the "isPublic" - metadata is found, before continuing further and testing whether an "API_KEY" is present.

In order to access the "Routes - Metadata" in our Guard. We’ll need to use a new "helper - class" called "Reflector".

The "Reflector" - class allows us to retrieve metadata within a specific context. This class is provided out of the box by the Nest - framework, from the @nestjs/core - package.

Let’s inject the "Reflector" - class here, inside of our "constructor()".

// api-key.guard.ts
import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common";
import { Reflector } from "@nestjs/core";           // <<<
import { ConfigService } from "@nestjs/config";     // <<<
import { Observable } from "rxjs";
import { Request } from "express";

import { IS_PUBLIC_KEY } from "../decorators/public.decorator";

@Injectable()
export class ApiKeyGuard implements CanActivate {
    constructor(
        private readonly reflector: Reflector,          // <<<
        private readonly configService: ConfigService,  // <<<
    ) {}

    canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
        const isPublic = this.reflector.get(IS_PUBLIC_KEY, context.getHandler())    // <<<

        if (isPublic) {
            return true
        }

        const request = context.switchToHttp().getRequest<Request>();
        const authHeader = request.header("Authorization");

        return authHeader === this.configService.get("API_KEY");
    }
}

Now we can use this Provider within the "CanActivate()" - method, to retrieve the metadata of our handler.

Let’s add a new variable called "isPublic". With this variable, let’s utilize "this.reflector.get()" which looks up metadata by its "key". In our case the exported variable "IS_PUBLIC_KEY", we just made a moment a go.

"Reflector" is requires a "target object context", for the "second - parameter", in our case we want to target the "method - handler" in our "given - context". So let’s pass in "context.getHandler()".

Note
Just for reference. If you need to retrieve metadata from a "Class - level", you’d call "context.getClass()" here instead.
Note
For more information on "Reflector" and other possibilities here. Read more about it in NestJS documentation

Okay. Great. Now that we have the "value" from our decorator inside of our Guard, there’s one last thing we need to do.

If a route is "public". We can simply skip the validation of the "API_KEY".

Let’s add a simple if - statement. If "isPublic" is "true", let’s just "return true".

Lastly, we mentioned earlier that we shouldn’t use "process.env" directly. So let’s fix that and instead use the "ConfigService".

Let’s move into the "constructor()" real quick and inject "ConfigService".

With this "Service - injected", we can now replace the previously used "process.env.API_KEY" call, with: this.configService.get() pasing in the string of "API_KEY".

Let’s save our changes and check the terminal to make sure everything compiling correctly.

$ npm run start:dev

src/main.ts:20:25 - error TS2554: Expected 2 arguments, but got 0.

20     app.useGlobalGuards(new ApiKeyGuard());
                           ~~~~~~~~~~~~~~~~~

  src/common/guard/api-key.guard.ts:12:9
    12         private readonly reflector: Reflector,
               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    An argument for 'reflector' was not provided.

[11:18:22 PM] Found 1 error. Watching for file changes.

OK, so our Guard is ready, but it looks like the application couldn’t bootstrap properly, and it is throwing compilation errors!. What happened? Well, this errors appears because we’re using "Dependency Injection" inside of our Guard, which was instantiated in the "main.ts" - file.

So how can we fix this?

As we showed in previous lesson. "Global Guards" that depend on other "Classes" MUST be registered within a " "@Module" - context".

Let’s fix this real quick, and add this Guard to a Module; and actually let’s take it up a notch, and create a "new - Module" for our "common" - folder, and we can instantiated our Guard there.

Let’s fire up another terminal window, and let’s generate a Module and call it "common" with

$ nest g module common
CREATE src/common/common.module.ts (83 bytes)
UPDATE src/app.module.ts (1775 bytes)

This will generate a "Module - class" where we can register any "global - enhancers" we might make in the future including our "ApiKeyGuard".

Great. So let’s open up our "new - Module" and utilize the "custom - provide" setup we learned in a previous lessons.

// common.module.ts
import { Module } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
import { APP_GUARD } from "@nestjs/core";
import { ApiKeyGuard } from "../common/guard/api-key.guard";

@Module({
    imports: [ConfigModule],
    providers: [
        {
            provide: APP_GUARD,
            useClass: ApiKeyGuard,
        },
    ],
})
export class CommonModule {}

This is where we pass in an "object" into our " provider - Array" providing a specific "Class" or "Key" and then "Value" to be used for it.

Inside of our @Module() - decorator. Let’s add a providers: [] Array, and pass in an Object with "provide: APP_GUARD" and "useClass: ApiKeyGuard".

This setup is very similar to using "app.useGlobalGuards()" that we had in our "main.ts" - file. But as we said, that option is only available if our Guard DO NOT use "Dependency - Injection".

Note
There is a way around that, but it isn’t best practice since you’d have to manually pass in dependencies.

One last thing here. Let’s make sure to import "ConfigModule" in the "import: []" Array here, so we can use "ConfigService" in our Guard.

Now let’s open up our "main.ts" - file and remove that "useGlobalGuards" line, since we don’t need it anymore and save all of our changes.

import { NestFactory } from "@nestjs/core";
import { HttpException, ValidationPipe } from "@nestjs/common";

import { AppModule } from "./app.module";
import { ApiKeyGuard } from "./common/guard/api-key.guard";

async function bootstrap() {
    const app = await NestFactory.create(AppModule);

    app.useGlobalPipes(
        new ValidationPipe({
            whitelist: true,
            transform: true,
            forbidNonWhitelisted: true,
            transformOptions: {
                enableImplicitConversion: true,
            },
        }),
    );
    await app.listen(3002);

    // console.log("app is run on port: 3002");
}
bootstrap();

Let’s switch to our terminal window again to make sure everything can bootstrap properly now.

$ npm run start:dev
[!] DatabaseModule.register() - instantiated
[Nest] 1648627   - 04/07/2021, 1:35:56 AM   [NestFactory] Starting Nest application...
[!!] CoffeesModule - instantiated
[Nest] 1648627   - 04/07/2021, 1:35:56 AM   [InstanceLoader] TypeOrmModule dependencies initialized +57ms
[Nest] 1648627   - 04/07/2021, 1:35:56 AM   [InstanceLoader] DatabaseModule dependencies initialized +0ms
[Nest] 1648627   - 04/07/2021, 1:35:56 AM   [InstanceLoader] DatabaseModule dependencies initialized +1ms
[Nest] 1648627   - 04/07/2021, 1:35:56 AM   [InstanceLoader] ConfigHostModule dependencies initialized +1ms
[Nest] 1648627   - 04/07/2021, 1:35:56 AM   [InstanceLoader] ConfigModule dependencies initialized +1ms
[Nest] 1648627   - 04/07/2021, 1:35:56 AM   [InstanceLoader] ConfigModule dependencies initialized +0ms
[Nest] 1648627   - 04/07/2021, 1:35:56 AM   [InstanceLoader] AppModule dependencies initialized +1ms
[Nest] 1648627   - 04/07/2021, 1:35:56 AM   [InstanceLoader] ConfigModule dependencies initialized +0ms
[Nest] 1648627   - 04/07/2021, 1:35:56 AM   [InstanceLoader] CommonModule dependencies initialized +0ms
[Nest] 1648627   - 04/07/2021, 1:35:56 AM   [InstanceLoader] TypeOrmCoreModule dependencies initialized +154ms
[Nest] 1648627   - 04/07/2021, 1:35:56 AM   [InstanceLoader] TypeOrmModule dependencies initialized +1ms
[!!] CoffeesService - instantiated
[!!] ConfigService - instantiated | "DATABASE_FOO": - bar
[Nest] 1648627   - 04/07/2021, 1:35:56 AM   [InstanceLoader] CoffeeRatingModule dependencies initialized +2ms
[Nest] 1648627   - 04/07/2021, 1:35:56 AM   [InstanceLoader] CoffeesModule dependencies initialized +0ms
[Nest] 1648627   - 04/07/2021, 1:35:56 AM   [RoutesResolver] AppController {}: +9ms
[Nest] 1648627   - 04/07/2021, 1:35:56 AM   [RouterExplorer] Mapped {, GET} route +6ms
[Nest] 1648627   - 04/07/2021, 1:35:56 AM   [RoutesResolver] CoffeesController {/coffees}: +1ms
[Nest] 1648627   - 04/07/2021, 1:35:56 AM   [RouterExplorer] Mapped {/coffees, GET} route +1ms
[Nest] 1648627   - 04/07/2021, 1:35:56 AM   [RouterExplorer] Mapped {/coffees/:id, GET} route +0ms
[Nest] 1648627   - 04/07/2021, 1:35:56 AM   [RouterExplorer] Mapped {/coffees, POST} route +1ms
[Nest] 1648627   - 04/07/2021, 1:35:56 AM   [RouterExplorer] Mapped {/coffees/:id, PATCH} route +0ms
[Nest] 1648627   - 04/07/2021, 1:35:56 AM   [RouterExplorer] Mapped {/coffees/:id, DELETE} route +1ms
[Nest] 1648627   - 04/07/2021, 1:35:56 AM   [NestApplication] Nest application successfully started +4ms

Great. Everything’s is compiling again.

Now, let’s open up insomnia and put our new Guard to the test. Let’s start by removing the "Authorization - header", and make a GET /coffees - request, which we setup to use our @Public() - decorator. If everything works as expected it should not" return a "403 Error". Since our Guard *should allow access to it.

// request: 'GET - http://localhost:3002/coffees/'

// Body - raw: JSON
{}

// Header
{}

// response, 200 -  OK
[
    {
        "id": 1,
        "title": "Salemba Roast#1",
        "description": null,
        "brand": "Salemba Brew",
        "recomendations": 0,
        "flavors": [
            {
                "id": 1,
                "name": "chocolate"
            },
            {
                "id": 2,
                "name": "vanilla"
            }
        ]
    },
    {
        "id": 2,
        "title": "Salemba Roast#2",
        "description": null,
        "brand": "Salemba Brew",
        "recomendations": 0,
        "flavors": []
    },
    {
        "id": 3,
        "title": "Salemba Roast#3",
        "description": null,
        "brand": "Salemba Brew",
        "recomendations": 0,
        "flavors": []
    }
]

It worked, great!.

Next, let’s try a Route that doesn’t have the @Public() - decorator, like a GET - request to the endpoint /coffees/1. Our Guard should deny us access to this Route, since it is neither "public" nor are we passing in an "Authorization - header".

// request: 'GET - http://localhost:3002/coffees/1'

// Body - raw: JSON
{}

// Header
{}

// response, 403 -  FORBIDDEN
{
    "statusCode": 403,
    "message": "Forbidden resource",
    "error": "Forbidden"
}

There it is. "403 - Forbidden". Our Guard works perfectly!.

So to wrap up, we covered a lot of ground (sublime) in this lesson. Tapping into a lot of concepts we learned in previous lessons.

Now we have additional power to create our "own - decorators", and utilize "handler - Metadata", useful for anything our application might need in the future.

Add Pointcuts With Interceptor

chapter 6 17
Add Pointcuts With Interceptor

Interceptor have many useful capabilities inspired by the "Aspect Oriented Programming.[2] - technique". This technique aims to increase "modularity" by allowing the separation of "cross-cut" and "concerns".

Interceptor achieve this by adding "additional - behavior" to existing code, without modifying the code itself!.

chapter 6 18
Add Pointcuts With Interceptor -2

Interceptor make it possible for us to:

  1. Bind extra logic, "before" or "after - method" execution.

  2. Transform the "result" returned from a method.

  3. Transform the "Exception - thrown" from a method

  4. Extend basic "method - behavior"

  5. Completely "overriding a method" - depending on specific conditions. For example: doing something like caching - "various - responses".

All right. So to learn how Interceptor "work - conceptually". Let’s learn through an example use-case where we always want our responses, to be located within a " "data" - property" ("data: response_here"); and create a "new - Interceptor" to handle this for us.

Let’s call this "new - Interceptor" "WrapResponseInterceptor". This new Interceptor will handle ALL - "incoming - requests", and "WRAP" our data for us automatically.

To get started. Let’s generate an "Interceptor - class" using the Nest -CLI by entering:

$ nest g interceptor common/interceptor/wrap-response
CREATE src/common/interceptor/wrap-response.interceptor.spec.ts (217 bytes)
CREATE src/common/interceptor/wrap-response.interceptor.ts (318 bytes)
Note
We generated this Interceptor in the "/common/" - directory like we have many times before, since Interceptor isn’t specific to any specific domain.

Let’s open up this newly generated "WrapResponseInterceptor" - file and see what we have inside.

// wrap-response.interceptor.ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class WrapResponseInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle();
  }
}

The Nest - CLI generated an example Interceptor for us without any "business - logic".

Similar to Providers, an Interceptor is a Class with the "@Injectable()" - decorator. All Interceptor should implement the NestInterceptor - interface exported from @nestjs/common - package.

This interface requires that we provide the intercept() - method within our class. The intercept() - method should return an "Observable" from the "RxJS - library".

If you are not familiar with RxJS. It is a library for "Reactive Programming using Observables". Making it easier to "compose - asynchronous" or "callback" - "base code".

RxJS itself is outside of the scope of what we can dive into here. Just know that it’s powerful alternative to "Promises or callbacks".

Back to our code. The "CallHandler" - interface here implement the "handler()" - method ("next.handle()"), which you can use to invoke the "Route - handler" - method within your Interceptor.

If you don’t call the "handle()" - method in your implementation of the intercept() - method. The "Route - handler" - method WONT be executed at all.

This approach means that the intercept() - method effectively "wrap" the Request/Response - stream, allowing us to implement "custom - logic" both "before" and "after" the execution of the final - "Route - handler".

All right. So we’ve covered a lot of theory so far.

Let’s add some "console.log()'s", to see where Interceptors fit in the "Request/Response" - life-cycle.

// wrap-response.interceptor.ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from "rxjs/operators";

@Injectable()
export class WrapResponseInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {

      console.log("Before..");

    return next.handle().pipe(tap(data => console.log("After..", data)));
  }
}

Since we’ve called the console.log() "before" calling the "next.handle()" - method.

Our "Before.." - message should appear in the console BEFORE the actual "Route - handler" is executed by the framework.

Since "handle" return an RxJS - Observable. We have a wide choice of operators we can use to manipulate the "stream" here.

In this example. We’re using the "tap()" - operator which invokes an anonymous logging function upon graceful termination of the "Observable - stream". But (the tap() - operator) doesn’t otherwise interfere with the response - cycle at all.

The "data" - argument of the arrow function ("⇒") and we passed into the tap() - operator here, is in fact the response sent back from the "Route - handler"!.

Basically think of this as whatever comes back from our endpoint!.

Here. We’re just doing a console.log() to say "After…​", and logging that "data" back as well.

To test it all out. Let’s bind this Interceptor to our application globally.

Let’s open our "main.ts" - file and add, "app.useGlobalInterceptors()", passing in "new WrapResponseInterceptor()".

// main.ts
import { NestFactory } from "@nestjs/core";
import { HttpException, ValidationPipe } from "@nestjs/common";

import { AppModule } from "./app.module";
import { ApiKeyGuard } from "./common/guard/api-key.guard";
import { WrapResponseInterceptor } from "./common/interceptor/wrap-response.Interceptor";

async function bootstrap() {
    const app = await NestFactory.create(AppModule);

    app.useGlobalPipes(
        new ValidationPipe({
            whitelist: true,
            transform: true,
            forbidNonWhitelisted: true,
            transformOptions: {
                enableImplicitConversion: true,
            },
        }),
    );
    app.useGlobalInterceptors(new WrapResponseInterceptor());
    await app.listen(3002);

    // console.log("app is run on port: 3002");
}
bootstrap();

Let’s make sure our application is running in the background and let’s navigate to insomnia and test any endpoint in our application.

First let’s make sure we have some "Coffees" stored in our database. If you have "Coffees" on your database you can skip this action.

To do so. Let’s hit the POST /coffees/ - endpoint and add a random "Coffee".

// request: 'POST - http://localhost:3002/coffees/'

// Body - raw: JSON
[
    {
        "title": "Salemba Roast#1",
        "description": null,
        "brand": "Salemba Brew",
        "flavors": [ "chocolate", "vanilla" ]
    },
    {
        "title": "Salemba Roast#2",
        "description": null,
        "brand": "Salemba Brew",
        "flavors": []
    },
    {
        "title": "Salemba Roast#3",
        "description": null,
        "brand": "Salemba Brew",
        "recomendations": 0,
        "flavors": []
    }
]

// Header
{}

// response, 200 -  OK
[
    {
        "id": 1,
        "title": "Salemba Roast#1",
        "description": null,
        "brand": "Salemba Brew",
        "recomendations": 0,
        "flavors": [
            {
                "id": 1,
                "name": "chocolate"
            },
            {
                "id": 2,
                "name": "vanilla"
            }
        ]
    },
    {
        "id": 2,
        "title": "Salemba Roast#2",
        "description": null,
        "brand": "Salemba Brew",
        "recomendations": 0,
        "flavors": []
    },
    {
        "id": 3,
        "title": "Salemba Roast#3",
        "description": null,
        "brand": "Salemba Brew",
        "recomendations": 0,
        "flavors": []
    }
]

Perfect. Now let’s hit the GET /coffees/ - endpoint and switch back to terminal to see those console.log()'s.

$ npm run start:dev
[Nest] 2646412   - 04/07/2021, 1:25:26 PM   [NestApplication] Nest application successfully started +5ms
[!!] CoffeesController created
Before..
After... [
  Coffee {
    id: 1,
    title: 'Salemba Roast#1',
    description: null,
    brand: 'Salemba Brew',
    recomendations: 0,
    flavors: [ [Flavor], [Flavor] ]
  },
  Coffee {
    id: 2,
    title: 'Salemba Roast#2',
    description: null,
    brand: 'Salemba Brew',
    recomendations: 0,
    flavors: []
  },
  Coffee {
    id: 3,
    title: 'Salemba Roast#3',
    description: null,
    brand: 'Salemba Brew',
    recomendations: 0,
    flavors: []
  }
]

As we can see, "Before…​" was logged; and then "After..", followed by the actual "value" from our "findAll()" - method, which represents the /coffees/ - endpoints. It looks like our Interceptor worked!.

Let’s open up insomnia again, and test another endpoint. Let’s test GET /coffees/1 - endpoint this time.

// request: 'POST - http://localhost:3002/coffees/'

// Body - raw: JSON
{}
// Header
{}

// response, 200 -  OK
{
    "id": 1,
    "title": "Salemba Roast#1",
    "description": null,
    "brand": "Salemba Brew",
    "recomendations": 0,
    "flavors": [
        {
            "id": 1,
            "name": "chocolate"
        },
        {
            "id": 2,
            "name": "vanilla"
        }
    ]
}

Let’s going back into terminal. This time we should see a "single - Object" following the "After…​" - log in our terminal.

$ npm run start:dev
[Nest] 2646412   - 04/07/2021, 1:25:26 PM   [NestApplication] Nest application successfully started +5ms
[!!] CoffeesController created
Before..
After... Coffee {
  id: 1,
  title: 'Salemba Roast#1',
  description: null,
  brand: 'Salemba Brew',
  recomendations: 0,
  flavors: [
    Flavor { id: 1, name: 'chocolate' },
    Flavor { id: 2, name: 'vanilla' }
  ]
}

Great, so far so good.

So now that we could see how Interceptors work and their part in the "Request/Response" - life-cycle.

Let’s implement our "data - wrapper" idea we talked about at the beginning of this lesson, and "wrap" our Response’s inside of a " data - property".

To do this, let’s replace the "tap()" - function with the "map()" - operator.

// wrap-response.interceptor.ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap, map } from "rxjs/operators";      //<<

@Injectable()
export class WrapResponseInterceptor implements NestInterceptor {
    intercept(context: ExecutionContext, next: CallHandler): Observable<any> {

        console.log("Before..");

        // return next.handle().pipe(tap(data => console.log("After...", data)))
        return next.handle().pipe(map((data) => ({data})));     // <<<
    }
}

The map() - operator takes a "value" from the "stream" and returns a modified one. Since we wanted to wrap all "Responses" in the "data" - property. Let’s return an Object "{}" with a "key/value" of "data".

Every time this map() - function s called, it return a "new - Object" with a "data" - property filled with our "original - Response".

Note
Remember!, everything we’ve done here is mainly for demonstration purposes.

Hopefully now you can see the power of Interceptors, and how there’s a potential to do so many other things here, like: "passing down version number", "analytics - tracking", etc..

All right. So let’s save all of our changes open up insomnia and test out this Interceptor. Let’s execute any "HTTP - Request" and see what the application send back to us.

// request: 'GET - http://localhost:3002/coffees/'

// Body - raw: JSON
{}

// Header
{}

// response, 200 -  OK
{
    "data": {
        "id": 1,
        "title": "Salemba Roast#1",
        "description": null,
        "brand": "Salemba Brew",
        "recomendations": 0,
        "flavors": [
            {
                "id": 1,
                "name": "chocolate"
            },
            {
                "id": 2,
                "name": "vanilla"
            }
        ]
    }
}

Perfect as we can see, the Response was automatically wrapped in an Object within the "data" - property.

So, wrapping up in this lesson we’ve show how Interceptor give us an incredible power to manipulate "Requests" OR "Responses", without changing ANY underlying code.

// main.ts
import { NestFactory } from "@nestjs/core";
import { HttpException, ValidationPipe } from "@nestjs/common";

import { AppModule } from "./app.module";
import { ApiKeyGuard } from "./common/guard/api-key.guard";
import { WrapResponseInterceptor } from "./common/interceptor/wrap-response.Interceptor";

async function bootstrap() {
    const app = await NestFactory.create(AppModule);

    app.useGlobalPipes(
        new ValidationPipe({
            whitelist: true,
            transform: true,
            forbidNonWhitelisted: true,
            transformOptions: {
                enableImplicitConversion: true,
            },
        }),
    );
    app.useGlobalInterceptors(new WrapResponseInterceptor());
    await app.listen(3002);

    // console.log("app is run on port: 3002");
}
bootstrap();

Handling Timeouts With Interceptors

chapter 6 19
Handling Timeouts With Interceptors

So far we’ve learned how to use Interceptor’s to bind "extra logic" before and after "method execution", as well as automatically logging and transforming results returned from "Route - handers".

Another technique useful for Interceptor is to extend the basic function behavior by applying "RxJS - operators" to the "Response - Stream".

Let’s see this in action with a realistic example.

To help us learn about this concept by example. Let’s imagine that we need to handle "Timeouts" for all of our "Route - requests".

When an endpoint does not return anything after a certain period of time, we need to "terminate" the Request, and send back an "Error - message".

Let’s start by generating another Interceptor, naming it "timeout", and also placing it in the /common/interceptor - folder.

$ nest g interceptor common/interceptor/timeout
CREATE src/common/interceptor/timeout.interceptor.spec.ts (196 bytes)
CREATE src/common/interceptor/timeout.interceptor.ts (313 bytes)

Let’s open up this newly generated "TimeoutInterceptor", and add some logic in there.

// timeout.interceptor.ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from "@nestjs/common";
import { Observable } from "rxjs";
import { timeout } from "rxjs/operators";

@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
    intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
        return next.handle().pipe(timeout(3000));
    }
}

In our example we want to terminate a "Request - handler" after a specific period of time. So let’s use the timeout() - operator imported from "rxjs/operators" to achieve this.

To demo this easily. Let’s set a very quick timeout() of 3000 - milliseconds. This means that when a request is made, after 3 - seconds, the Request processing will be automatically cancelled for us.

To test it out. Let’s bind this second Interceptor to our application globally, and since it has no dependencies, we can add this in our "`main.ts"` - file.

// main.ts
import { NestFactory } from "@nestjs/core";
import { HttpException, ValidationPipe } from "@nestjs/common";

import { AppModule } from "./app.module";
import { ApiKeyGuard } from "./common/guard/api-key.guard";
import { WrapResponseInterceptor } from "./common/interceptor/wrap-response.Interceptor";
import { TimeoutInterceptor } from "./common/interceptor/timeout.interceptor";

async function bootstrap() {
    const app = await NestFactory.create(AppModule);

    app.useGlobalPipes(
        new ValidationPipe({
            whitelist: true,
            transform: true,
            forbidNonWhitelisted: true,
            transformOptions: {
                enableImplicitConversion: true,
            },
        }),
    );
    app.useGlobalInterceptors(
        new WrapResponseInterceptor(),
        new TimeoutInterceptor()
    );
    await app.listen(3002);

    // console.log("app is run on port: 3002");
}
bootstrap();
Note
We can actually bind - "multiple - Interceptors" here simply by separating them with commas.

Now to make sure that our Interceptor works properly. Let’s open up the "CoffeesController" - file and temporarily add a "setTimeout()" - function in our findAll() - method to simulate a very long delay.

// coffees.controller.ts
...
...

@Controller("coffees")
export class CoffeesController {
    ...
    ...

    @Public()
    @Get()
    async findAll(@Query() paginationQuery: PaginationQueryDto) {
        await new Promise(resolve => setTimeout(resolve, 5000));

        return this.coffeesService.findAll(paginationQuery);
    }
    ...
    ...
}

Let’s set a "timeout" for "5" - seconds, higher than the "3" - seconds we set in our Interceptor to purposely trigger our TimeoutInterceptor.

Perfect. Now let’s open up insomnia and execute this endpoint by making a Request for GET /coffees.

// request: 'POST - http://localhost:3002/coffees/'

// Body - raw: JSON
{}

// Header
{}

// response, 500 -  Internal Service Error
{
    "statusCode": 500,
    "message": "Internal server error"
}

We received a "500 Error" which means our TimeoutInterceptor worked!.

However the "Error - message" we got back is not really descriptive. It says "Internal server Error". But how we make this message more user-friendly?

Heading back to our TimeoutInterceptor again. Let’s chain another operator called catchError() inside our pipe() - method.

// timeout.interceptor.ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor, RequestTimeoutException } from "@nestjs/common";
import { Observable, TimeoutError, throwError } from "rxjs";
import { timeout, catchError } from "rxjs/operators";

@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
    intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
        return next.handle().pipe(
            timeout(3000),
            catchError((err) => {
                if (err instanceof TimeoutError) {
                    return throwError(new RequestTimeoutException());
                }
                return throwError(err);
            }),
        );
    }
}

The RxJS "catchError()" - operator allows us to catch all exceptions that occurred within the stream.

In the "callback - function" we provide here we can check if the error throne is an instance of TimeoutError. Which is also imported from rxjs.

If so, we use a utility - function throwError() from rxjs to create a stream which immediately emit an Error for whatever is passed into it.

Since we want our error message to be more specific for this scenario, let’s use a NestJS - Class called "RequestTimeoutException" imported from @nestjs/common, so we can throw the correct "error message" to our users letting them know that the Request has actually timed-out.

Great. So let’s save our changes navigate back to insomnia and test the endpoint once again.

// request: 'POST - http://localhost:3002/coffees/'

// Body - raw: JSON
{}

// Header
{}

// response, 480 - Request Timeout
{
    "statusCode": 408,
    "message": "Request Timeout"
}

As we can see. This time we received the 408 - Response with the descriptive message `"Request Timeout".

So in this lesson we learned how to add additional superpower to our Interceptors along with some new tricks from RxJS. All which are very common scenarios and helpful in most of our NestJS applications.

Creating Custom Pipes

chapter 6 20
Creating Custom Pipes

Pipes have 2 - "typical use-cases"

chapter 6 21
Creating Custom Pipes -2
  • Transformation. Where we transform "input - data" to the desired "output".

  • Validation. Where we evaluate - "input - data" and "if - valid", simply pass it through unchanged. If the data is "not - valid", we want to throw an Exception.

In both cases. Pipes operate on the arguments being processed by a Controller’s - "Route - handler".

chapter 6 22
Creating Custom Pipes -3

NestJS triggers a Pipe just before a method is invoked.

Pipes also receive the arguments meant to be passed onto the method. Any "transformation" or "validation - operation" takes place at-this-time. Afterwards the "Route - handler" is invoked with any potentially transformed arguments.

NestJS comes with several "Pipes - available" out of the box. All from the @nests/common - package. For example, "ValidationPipe", which we’ve seen in previous lessons, and "ParseArrayPipe", which we haven’t seen but it’s an extremely helpful Pipe that helps us "parse" and "validate - Arrays".

To learn how we can build our own "custom - Pipes". Let’s create that Pipe automatically parses any incoming String to an Integer, and let’s call it "ParseIntPipe".

Nest already has a "ParseIntPipe" that we could use from the @nestjs/common - library. But let’s create this basic Pipe for learning purpose, top help us "fully understand the basic mechanics of a Pipe".

Let’s fire up the terminal and generate a "Pipe - class" using the Nest - CLI "Pipe schematic", placing it in the common/pipe - folder and calling it "parse-int".

$ nest g pipe common/pipe/parse-int
CREATE src/common/pipes/parse-int.pipe.spec.ts (173 bytes)
CREATE src/common/pipes/parse-int.pipe.ts (224 bytes)
Note
We generated this Pipe in the `/common/ - directory again where we can keep things that aren’t tied to any specific domain.

Let’s open up this newly generated "ParseIntPipe" - file and see what we have inside.

// parse-int.pipe.ts
import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common';

@Injectable()
export class ParseIntPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    return value;
  }
}

As we can see the Nest - CLI generated an example Pipe for us without any logic inside. Similar to Providers a Pipe is a class with the @Injectable() - decorator. But all Pipes should implement the "PipeTransform" - interface exported from @nestjs/common.

This interface requires us to provide the "transform()" method within our Class. This "transform()" - method has 2 - parameters.

  • "value": The input value of the currently processed argument before it is received by or "Route - handling" - method.

  • "metadata": The metadata of the currently processed argument.

Whatever "value" is returned from this transform() - function completely overrides the previous - "value" of the argument.

So when is this useful?. Consider that sometimes the data - passed from the client, needs to undergo some change, before this data can be properly handled by the "Route - handler" - method.

Another use-case for Pipes would be to provide "default - values". Imagine if we had some "required - data fields" that were missing. We could automatically set these defaults within a Pipe.

"Transformer - Pipes" can perform these functions by interposing[4] the "transformation - function" we create between the "client - Request" - and the "Request - handler". This is merely another example. But the point is to show that there are many powerful things you can do with Pipes.

All right. So back to creating a custom "ParseIntPipe". Let’s start implementing the actual logic.

// parse-int.pipe.ts
import { ArgumentMetadata, BadRequestException, Injectable, PipeTransform } from "@nestjs/common";

@Injectable()
export class ParseIntPipe implements PipeTransform {
    transform(value: string, metadata: ArgumentMetadata) {
        const val = parseInt(value, 10);

        if (isNaN(val)) {
            throw new BadRequestException(`validation failed, "${val} is not an Integer.`);
        }

        return value;
    }
}

First. We can assume that the "input - value" or value is a String, so we can change the Type from "any" to "string".

Next. Let’s use the built-in parseInt() - JavaScript function to try parsing the "input - String" to an "Integer".

Let’s add some" conditional - logic" here in case we see any errors. If the return value is not a number we can make sure to throw a "BadRequestException", otherwise we can return the "modified - value" which is now an Integer!.

Within this in place, we can now bind our Pipe to some @Param() - decorators.

Let’s open up the "CoffeesController" - file and navigate to the findOne() - method.

// coffees.controller.ts
...
...

@Controller("coffees")
export class CoffeesController {
    ...
    ...

    @Get(":id")
    findOne(@Param("id") id: number) {
        console.log("GET ===>", id);
        return this.coffeesService.findOne("" + id);
    }
    ...
    ...
}

Before we do anything here. Let’s add a single console.log() to log the "id" - "arguments - value".

Now let’s navigate to insomnia and execute a GET - request to the /coffees/abc endpoint.

// request: 'POST - http://localhost:3002/coffees/abc'

// Body - raw: JSON
{}

// Header
{}

// response, 400 - Internal Server Error
{
    "statusCode": 500,
    "message": "Internal server error"
}
Note
We’re going to pass "abc", a String, instead of a number like "1" or "10".

Go back to our terminal and see what was logged.

$ npm run start:dev
...
...
[Nest] 3531343   - 04/07/2021, 10:49:17 PM   [NestApplication] Nest application successfully started +4ms
[!!] CoffeesController created
Before..
GET ===> NaN
[Nest] 3531343   - 04/07/2021, 10:49:18 PM   [ExceptionsHandler] invalid input syntax for type integer: "NaN" +1175ms
QueryFailedError: invalid input syntax for type integer: "NaN"
...
...

As we can see it output "NaN" or "Not a Number". This means that although our "id"- "abc" could not have been parsed to a Number, the findOne() - method was called with an incorrect argument.

To prevent situations like this. Let’s use our newly created "ParseIntPipe" to validate the "incoming - parameter" and in case it’s not parse-able to an Integer, our Pipe will automatically throw a "validation - exception" for us.

So let’s pass in our ParseIntPipe as the "second - parameter" to our @Param("id") in "CoffeesController".

// coffees.controller.ts
...
...
import { ParseIntPipe } from "../common/pipes/parse-int.pipe";

@Controller("coffees")
export class CoffeesController {
    ...
    ...

    @Get(":id")
    findOne(@Param("id", ParseIntPipe) id: number) {
        console.log("GET ===>", id);
        return this.coffeesService.findOne("" + id);
    }
    ...
    ...
}

Save the changes, and test the endpoint again.

// request: 'POST - http://localhost:3002/coffees/abc'

// Body - raw: JSON
{}

// Header
{}

// response, 480 - Request Timeout
{
    "statusCode": 400,
    "message": "validation failed, \"NaN is not an Integer.",
    "error": "Bad Request"
}

Great. As we can see this time, we received a "400 - Error" with the message stating that "validation failed".

As we said, Nest comes with this Pipe already, but hopefully this example showcases the power of Pipes and how you could implement your own to do a multitude of useful processes for your application.

Bonus: Add Request Logging With Middleware

chapter 6 23
Add Request Logging With Middleware

Middleware is a function that is called before the "Route - handler" and any other "building - blocks" are processed. This includes "Interceptor", "Guards" and "Pipes".

chapter 6 24
Add Request Logging With Middleware - 2

Middleware - function have access to the "Request" and "Response - object", and are not specifically tied to any - method, but rather to a specified "Route - path".

Middleware - function can perform the following tasks:

  • Executing code.

  • Making changes to the Request and Response - objects.

  • Ending the "Request/Response" - cycle.

  • Calling the "next()" Middleware - function in the "call - stack".

When working with Middleware, if the current "Middleware - function" does not end the "Request/Response -cycle". It MUST call the "next()" - method, which passes control to the next - "Middleware - function". Otherwise the Request will be left "-Hanging-", and never complete.

So how can we get started creating our own Middleware?.

chapter 6 25
Add Request Logging With Middleware - 3

Custom Nest Middleware can be implemented in either a "Function" or a "Class".

"Function - Middleware" is "stateless", it can NOT inject dependencies, and doesn’t have access to the "Nest - container".

On the other hand, "Class - Middleware" can rely on "external - dependencies" and "inject - Providers" registered in the same "Module - scope".

In this lesson, we’ll focus on building a "Class - Middleware". But remember you can always use "Functions" to create them as well.

Let’s fire up the terminal, and generate a "Middleware - Class" using the Nest - CLI and let’s call it "logging".

$ nest g middleware common/middleware/logging
CREATE src/common/middleware/logging.middleware.spec.ts (192 bytes)
CREATE src/common/middleware/logging.middleware.ts (199 bytes)
Note
We generate this Middleware in the "/common/" - directory, again since it isn’t tied to any specific domain.

Let’s open up this newly generated "LoggingMiddleware" - file and see what we have inside.

// logging.middleware.ts
import { Injectable, NestMiddleware } from "@nestjs/common";

@Injectable()
export class LoggingMiddleware implements NestMiddleware {
    use(req: any, res: any, next: () => void) {
        console.log("Hi from Middleware!");
        next();
    }
}

As we can see the Nest - CLI generated an example Middleware without any logic inside. Similar to Providers, a Middleware is a Class with the @Injectable() - decorator.

All the Middleware should implement the NestMiddleware - interface exported from @nestjs/common. This interface requires us to provide the "use()" - method within our class. This method, does not have any special requirements.

Note
Just remember to always call the "next()" - function otherwise the Request will be left hanging.

For now we’ll keep things simple, let’s just add a console.log() before invoking the "next()" - function to see how Middleware fits into the "Request/Response - life-cycle".

With this in place we can now register our newly created Middleware.

As we’ve mentioned earlier, Middleware aren’t specifically tied to any method. We can not bind them in a declarative way using decorators. Instead we bind Middleware to a 'Route - path", represented as a String.

To register our LoggingMiddleware. Let’s open up our `CommonModule" - file.

// common.module.ts
import { MiddlewareConsumer, Module, NestModule } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
import { APP_GUARD } from "@nestjs/core";
import { ApiKeyGuard } from "../common/guard/api-key.guard";

import { LoggingMiddleware } from "./middleware/logging.middleware";

@Module({
    imports: [ConfigModule],
    providers: [
        {
            provide: APP_GUARD,
            useClass: ApiKeyGuard,
        },
    ],
})
export class CommonModule implements NestModule {
    configure(consumer: MiddlewareConsumer) {
        consumer.apply(LoggingMiddleware).forRoutes("*");
    }
}

First let’s make sure that our "CommonModule" - class implements the "NestModule" - interface. This interface requires us to provide the "configure() - method, which takes the "MiddlewareConsumer" as an argument.

MiddlewareConsumer provides a set of useful methods to tie Middleware to specific Routes.

Just to test it out. Let’s apply the "LoggingMiddleware" to "all - Routes", using the Asterisk (" * "), or "wildcard - operator".

To add our Middleware. Let’s call the "apply()" - method on a "consumer", passing in our "LoggingMiddleware" and then let’s call the ".forRoutes(*)" - method, passing in the "wildcard Asterisk (" * ")".

Let’s save our changes, and navigate to insomnia and perform an HTTP - Request to any endpoint.

Let’s open the terminal, and see if anything popped up.

$ npm run start:dev
...
...
[Nest] 3831529   - 04/08/2021, 2:23:05 AM   [NestApplication] Nest application successfully started +5ms
Hi from Middleware!
[!!] CoffeesController created
Before..

Great we can see `"Hi from Middleware" string in our console.

Now taking a step back to our Middleware "consumer". There are several other ways of tying Middleware to different "Routes - paths".

Let’s go back to "CommonModule" - file.

So far we’ve bound the "LoggingMiddleware" to every route using the "Asterisk " * " wildcard". But we can also apply only to Roures with, let’s say the "coffees" - prefix if we want.

// common.module.ts
...
...
@Module({
    ...
    ...
})
export class CommonModule implements NestModule {
    configure(consumer: MiddlewareConsumer) {
        consumer.apply(LoggingMiddleware).forRoutes("coffees");
                                                     ~~~~~~~
    }
}

We can even restrict Middleware further if we’d like let’s say to a particular "Request - method" like only for "GET" - methods, by passing an Object containing the 'Route - path" and "RequestMethod"

// common.module.ts
...
...
@Module({
    ...
    ...
})
export class CommonModule implements NestModule {
    configure(consumer: MiddlewareConsumer) {
        consumer.apply(LoggingMiddleware).forRoutes({ path: "coffees", method: RequestMethod.GET });
                                                      ~~~              ~~~~~~  ~~~~~~~~~~~~~
    }
}

Note that, we imported the "RequestMethod" - Enum we used in previous lessons to reference desired "Request - method" - Type.

Lastly, we can also "exclude" - certain Routes from having the Middleware applied with the "exclude()" - method.

// common.module.ts
...
...
@Module({
    ...
    ...
})
export class CommonModule implements NestModule {
    configure(consumer: MiddlewareConsumer) {
        consumer.apply(LoggingMiddleware).exclude.forRoutes("*");
                                         ~~~~~~~
    }
}

This "exclude()" - method can take a "single - String", "multiple - String" or "RouteInfo{}" - Object. Identifying Routes to be excluded.

For example we can apply the Middleware to every Route -except- for those with the ""coffees - prefix" if we wanted.

Now that we’ve looked at a few of the options available to use here. Let’s revert back to our original "forRoutes()" - wildcard wince we want to bind the "LoggingMiddleware" to *every existing endpoint for now.

Let’s open up the "LoggingMiddleware" file again and add some additional functionality in here.

For a fun example, let’s calculate "how long" the entire "Request/Response" - cycle takes, by using console.time().

// logging.middleware.ts
import { Injectable, NestMiddleware } from "@nestjs/common";

@Injectable()
export class LoggingMiddleware implements NestMiddleware {
    use(req: any, res: any, next: () => void) {
        console.time("[!!] Request-response time");
        console.log("[!!] Hi from Middleware!");

        res.on("finish", () => console.timeEnd("[!!] Request-response time"));
        next();
    }
}
Note
This calculation will include the Interceptors, Filters, Guards, "method - handler" etc. That this route may have as well!.

In this example we’re hooking into the ExpressJS "Response - finish" - event so we know when our console.timeEnd() should occur.

Let’s save our changes and navigate to insomnia.

Now let’s execute a random HTTP - request to our application, and head back to the terminal.

$ npm run start:dev
...
...
[Nest] 529632   - 04/08/2021, 12:55:40 PM   [NestApplication] Nest application successfully started +5ms
[!!] Hi from Middleware!
[!!] CoffeesController created
Before..
[!!] Request-response time: 27.496ms

As we can see there’s a new "Request/Response - message" indicating that the full round-trip took a roughly "27 milliseconds".

This was all a basic example, but hopefully this shows you the potential that Middleware brings to an application. For a more realistic use case you could potentially utilize something like what we just created, to log "long lasting methods" to a database, and keep track of how long every API takes to complete.

Bonus: Create Custom Param Decorators

A lot of NestJS is built around TypeScript language feature called "decorators". Decorators are simply functions that apply logic.

// coffees.controller.ts
...
...
@Patch(":id")
update(@Param("id") id: string, @Body(ValidationPipe) updateCoffeeDto: UpdateCoffeeDto) {
    // ~~~~                     ~~~~
    return this.coffeesService.update(id, updateCoffeeDto);
}
...
...

NestJS provides a set of useful "param - decorators" that you can use together with the HTTP - "Route - handler", for example "@Body()", to extract the "request.body". Or "@Param()", to pick a specific "request - parameter". Additionally we can create our own "custom - decorators".

// coffees.controller.ts
...
...
@Patch(":id")
update(@Req("id") request, @Param("id") id: string, @Body(ValidationPipe) updateCoffeeDto: UpdateCoffeeDto) {
    // ~~~~
    return this.coffeesService.update(id, updateCoffeeDto);
}
...
...

Let’s imagine that for some reason we want to retrieve the "request.protocol" from within the "Route - handler". Normally we would need to inject the entire "Request - Object" wit the @Req() - decorator into method definition.

However this makes this particular method harder to test since we would need to mock the entire "Request - Object" every time we try to test this method.

In order to make our code more readable and easier to test. Let’s create a custom @Param() - decorator instead.

To get started let’s open the /common/decorators - folder, and create a "protocol.decorator.ts" inside.

// protocol.decorator.ts
import { createParamDecorator, ExecutionContext } from "@nestjs/common";

export const Protocol = createParamDecorator(
    (data: unknown, ctx: ExecutionContext) => {
        const request = ctx.switchToHttp().getRequest();

        return request.protocol;
    }
)

Now inside this file, let’s make use of this file, let’s make use of the "utility - function": "CreateParamDecorator" imported from @nestjs/common to build our "custom - decorator".

Since we’re trying to find the "request.protocol" for our decorator, we’re going to need to retrieve it form the "Request - object". We can retrieve this from "ExecutionContext" we’ve seen in previous lessons, with switchToHttp(), and then calling getRequest().

Afterwards let’s simply return request.protocol and we’re all set.

Great, now to test our decorator. Let’s open up our "CoffeesController" - file and temporarily use it on the findAll() - method.

// coffees.controller.ts
...
...
@Controller("coffees")
export class CoffeesController {
    constructor(
    ...
    ...
    ) {
        console.log("[!!] CoffeesController created");
    }

    @Public()
    @Get()
    async findAll(@Protocol() protocol: string, @Query() paginationQuery: PaginationQueryDto) {
                // ~~~~~~
        console.log(`[!!] Protocol instantiated: "${protocol}"`);
     // ~~~~~~~~~~

        return this.coffeesService.findAll(paginationQuery);
    }
}
...
...

Let’s add a single console.log() with the method to log out this @Protocol() - parameter, and see what it gives us.

Let’s save our changes, navigate to insomnia and execute a GET /coffees - request to see everything in action.

Now, let’s head back to our terminal.

$ npm run start:dev
...
...
[Nest] 640992   - 04/08/2021, 2:15:20 PM   [NestApplication] Nest application successfully started +4ms
[!!] Hi from Middleware!
[!!] CoffeesController created
Before..
[!!] Protocol instantiated: "http"
[!!] Request-response time: 32.686ms
...
...

We could see "http", our protocol, was logged in the console. Great it works.

We can also pass "arguments" to our "custom - decorators" if needed. In this instance our @Protocol() - decorator is fully stateless, so there’s no reason to pass in any parameter since there’s nothing to configure.

However, in more sophisticated scenarios where the behavior of our decorator depends on different conditions, we can pass the "data" - argument into them.

For example, let’s say that we wanted to pass a 'default - value" to the decorator we just made. Let’s pass a string of "https", between the @Protocol("https") parentheses.

// coffees.controller.ts
...
...
@Controller("coffees")
export class CoffeesController {
    constructor(
    ...
    ...
    ) {
        console.log("[!!] CoffeesController created");
    }

    @Public()
    @Get()
    async findAll(@Protocol("https") protocol: string, @Query() paginationQuery: PaginationQueryDto) {
                	 // ~~~~~~
        console.log(`[!!] Protocol instantiated: "${protocol}"`);

        return this.coffeesService.findAll(paginationQuery);
    }
}
...
...

To access this "value" from within our @Param() - decorator factory. We can use the "data" - argument, the first argument here we previously hadn’t touched.

// protocol.decorator.ts
import { createParamDecorator, ExecutionContext } from "@nestjs/common";

export const Protocol = createParamDecorator(
    (data: unknown, ctx: ExecutionContext) => {
  // ~~~~
        const request = ctx.switchToHttp().getRequest();

        return request.protocol;
    }
)

To make our decorator more self explanatory. Let’s change the "argument - name" to "defaultValue:", and set it to type "string", for better readability and Type-safety.

// protocol.decorator.ts
import { createParamDecorator, ExecutionContext } from "@nestjs/common";

export const Protocol = createParamDecorator(
    (defaultValue: string, ctx: ExecutionContext) => {
  // ~~~~~~~~~~~
        console.log("[!!] Protocol value:", { defaultValue })
        const request = ctx.switchToHttp().getRequest();

        return request.protocol;
    }
)

With this in place. Now let’s see if these "values" are actually coming into our decorator, and add a single console.log(defaultValue) to log out this "default - value" to our terminal.

Again. Let’s make sure to save our changes, navigate to insomnia and execute the same GET /coffees - request. Back to our terminal again.

$ npm run start:dev
...
...
[Nest] 669039   - 04/08/2021, 2:35:09 PM   [NestApplication] Nest application successfully started +4ms
[!!] Hi from Middleware!
[!!] CoffeesController created
Before..
[!!] Protocol value: { defaultValue: 'https' }
[!!] Protocol instantiated: "http"
[!!] Request-response time: 32.14ms

We can see the "{ defaultValue: https }" Object was logged in the console. Great!.


1. Access Control List
2. In computing, aspect-oriented programming (AOP) is a programming paradigm that aims to increase modularity by allowing the separation of cross-cutting concerns.It does so by adding additional behavior to existing code (an advice) without modifying the code itself, instead separately specifying which code is modified via a "pointcut" specification, https://en.wikipedia.org/wiki/Aspect-oriented_programming
4. place between