Node.js Framework Series — 1.2.4. NestJS — Middleware

--

We can explain the middleware as a layer of code which is called before the route handler. We are able to access request object(req) and response object(res) and next function in the application’s request-response cycle by using middleware functions and by default NestJS middleware functions are equivalent to express middleware. Let’s have a look at what we can do by using express middleware:

  • Execute any code.
  • Make changes to the request and the response objects.
  • End the request-response cycle.
  • Call the next middleware in the stack.

The following figure shows the elements of a middleware function call:

Express middleware functions figure

We can implement our custom NestJS middleware as a function or a class with an @Injectable() decorator. To create a simple NestJS middleware, we inherit our class from NestMiddleware interface. It does not require anything special.

In general, as we see on the express middleware documentation, we can use it to run or manipulate any code between the request and response handler such as audit logs, tenant-based database connections (multi-tenant architecture and SaaS applications), Rest API securities, error handlings, guard specific routes and etc. We can figure NestJS middleware for the routing as follows:

NestJS Middleware Architecture

Let’s explain it with an example on our property user microservice. We can log all of the incoming request for the specific end point for the audit purpose as follows:

First, we create our middleware class.

auditlog.middleware.ts

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response } from 'express';
import * as circular from 'circular';
@Injectable()
export class AuditLogMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: Function) {
console.log(`Incoming request: ${JSON.stringify(req, circular())}`);
console.log(`Outgoing request: ${JSON.stringify(res, circular())}`);
next();
}
}

Then we inject it to a module as how we want to use, for instance as follows:

app.module.ts

import { AuditLogMiddleware } from './auditlog.middleware';
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { LoginController } from './login/login.controller';
import { RegisterController } from './register/register.controller';
import { ProfileModule } from './profile/profile.module';
import { NotificationModule } from './notification/notification.module';
@Module({
imports: [ProfileModule, NotificationModule],
controllers: [AppController, LoginController, RegisterController],
providers: [AppService],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(AuditLogMiddleware).forRoutes('profile');
}

}

We don’t have any property in the @Module() decorator for the middleware. Instead, we use NestModule interface that has a configure() method. So as above, we implement and inject our middleware to AppModule.

NestJS middleware supports DI(Dependency Injection), so we are able to inject all of the dependencies through the contractor.

We can also apply some filters to endpoints as follows:

export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(AuditLogMiddleware)
.forRoutes({ path: 'profile', method: RequestMethod.GET });
}
}

We applied our middleware to a specific HTTP method and path. So we can also restrict a middleware for the specific configurations as shown above.

We can apply pattern based routes to a middleware as well as follows:

forRoutes({ path: 'x*yz', method: RequestMethod.ALL });

Middleware consumer is a helper class and provides some built-in methods to manage middleware such as apply(), forRoutes() and exclude() methods. The forRoutes() method can take a single string, multiple strings, a RouteInfo object, a controller class and even multiple controller classes. Let’s have a look at how it is working with controller:

@Module({
imports: [ProfileModule, NotificationModule],
controllers: [AppController, LoginController, RegisterController],
providers: [AppService],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(AuditLogMiddleware).forRoutes(ProfileController);
}
}

We can exclude some certain routes as follows:

export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(AuditLogMiddleware)
.exclude(
{ path: 'register', method: RequestMethod.GET },
{ path: 'profile', method: RequestMethod.DELETE },
'profile/(.*)',
)
.forRoutes(ProfileController);

}
}

As we see above, this method can take single string, multiple or a RouteInfo object for excluding routes.

Instead of using a middleware class, we can use also functional middleware as follows:

audit-log.ts

import { Request, Response } from 'express';
import * as circular from 'circular';
export function auditLog(req: Request, res: Response, next: Function) {
console.log(`Incoming request: ${JSON.stringify(req, circular())}`);
next();
};

and we use it in the AppModule as follows:

consumer
.apply(auditLog)
.forRoutes(CatsController);

We can also apply multiple middleware to specific route or routes as follows:

consumer
.apply(cors(), helmet(), auditLog)
.forRoutes(CatsController);

NestJS will execute them sequentially.

If you want to bind middleware to every registered route, we can easily implement as follows in main.ts :

main.ts

const app = await NestFactory.create(AppModule);
app.use(auditLog);
await app.listen(3000);

Resources:

Writing middleware for use in Express apps, Express, 2020, available at https://expressjs.com/en/guide/writing-middleware.html

--

--

Rasim Sen/Blockchain Architect-zero2hero founder

I am a blockchain architect, close on 2 decades of experience as a developer, I’ve got 5 products in blockchain, and I like to talk about tech,sci-fi, futurism!