In this post, I’m going to cover the concept of middleware functions in NestJS.

Here’s what you are going to learn:

  • What is a middleware function?
  • How to apply a middleware function in NestJS?
    • method-level
    • route wildcards
    • excluding routes
    • multiple middleware functions
  • Functional middleware
  • Global middleware

NestJS middleware functions are an extremely important part of learning the NestJS fundamentals.

1 – What is a NestJS middleware function?

The term “middleware” might sound intimidating. But it’s just a fancy term for a function that’s called before the route handler.

Middleware functions get access to both request and response objects along with the next() middleware function.

You can use middleware functions to do all sorts of things such as:

  • logging requests
  • authentication & authorization
  • rate limiting
  • transform the request and response objects
  • and many more…
nestjs middleware functions
Middleware functions in NestJS

When it comes to NestJS, the middleware functions are equivalent to express middleware.

Here’s how you implement one in NestJS.

import { Injectable, NestMiddleware } from "@nestjs/common";
import { NextFunction, Request, Response } from "express";

@Injectable()
export class LoggingMiddleware implements NestMiddleware {
    use(req: Request, res: Response, next: NextFunction) {
        console.log(`Request object: ${JSON.stringify(req.headers)}`);
        next();
    }
}

This is known as the class-based middleware implementation. You can also implement using a simple function. We will see it in a later section of this post.

A couple of things to keep in mind:

  • The class should implement the NestMiddleware interface. This means you need to implement the use() method that comes with the interface
  • The class should be decorated with @Injectable() decorator. This is the same decorator you used while creating a NestJS Provider.

As you can see in the example, the use() function gets access to the request & response objects along with the next() function.

Within the function, you log the request headers. You can also log other parts of the request object depending on the requirement.

In case you have some other use case for the middleware, you can implement the desired logic within the use() function.

Nest middleware also supports dependency injection. Just like providers and controllers, you can inject dependencies that are available within the same module.

2 – How to apply a middleware in NestJS?

In the previous section, you simply declared by a middleware function. It is still not active or applied to your application.

You activate a middleware function using the configure() method of a module class.

See the below example:

import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ProductsModule } from './products/products.module';
import { ConfigurationModule } from './configuration/configuration.module';
import { LoggingMiddleware } from './middlewares/logging.middleware';

@Module({
  imports: [ProductsModule, 
    ConfigurationModule.register({db_url: '<http://localhost:27017>'})],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggingMiddleware)
      .forRoutes('products')
  }
}

The configure() method takes an instance of the MiddlewareConsumer class. This is a helper class that has several in-built methods to manage the middleware.

You can chain the methods in a descriptive manner. In the above example, you apply() the middleware by chaining the forRoutes('products') method. This tells NestJS to apply the LoggingMiddleware to all the routes starting with /products.

There are also other ways you can apply a middleware function:

Method-Based Selection

You can apply the middleware function to specific routes by passing an object containing the route path and the method type as input.

See the below example:

import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ProductsModule } from './products/products.module';
import { ConfigurationModule } from './configuration/configuration.module';
import { LoggingMiddleware } from './middlewares/logging.middleware';

@Module({
  imports: [ProductsModule, 
    ConfigurationModule.register({db_url: '<http://localhost:27017>'})],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggingMiddleware)
      .forRoutes({ path: 'products', method: RequestMethod.GET })
  }
}

Controller-Based Selection

Instead of the route path, you can also set a list of controllers in the forRoutes() method. You can read more about controllers in my detailed post on NestJS controllers.

See the below example where the middleware function is applied to all the route handlers in the ProductsController.

import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ProductsModule } from './products/products.module';
import { ConfigurationModule } from './configuration/configuration.module';
import { LoggingMiddleware } from './middlewares/logging.middleware';
import { ProductsController } from './products/products.controller';

@Module({
  imports: [ProductsModule, 
    ConfigurationModule.register({db_url: '<http://localhost:27017>'})],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggingMiddleware)
      .forRoutes(ProductsController)
  }
}

Pattern-Based Route Selection

The forRoutes() method also supports pattern-based route selection.

See the below example:

import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ProductsModule } from './products/products.module';
import { ConfigurationModule } from './configuration/configuration.module';
import { LoggingMiddleware } from './middlewares/logging.middleware';

@Module({
  imports: [ProductsModule, 
    ConfigurationModule.register({db_url: '<http://localhost:27017>'})],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggingMiddleware)
      .forRoutes({ path: 'x*z', method: RequestMethod.ALL})
  }
}

Here, the x*z path contains the asterisk as a wildcard that will match any combination of characters.

Excluding Routes

Sometimes, you might need to exclude certain routes. You can do so by chaining the exclude() method to the MiddlewareConsumer instance.

Check the below example:

import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ProductsModule } from './products/products.module';
import { ConfigurationModule } from './configuration/configuration.module';
import { LoggingMiddleware } from './middlewares/logging.middleware';
import { ProductsController } from './products/products.controller';

@Module({
  imports: [ProductsModule, 
    ConfigurationModule.register({db_url: '<http://localhost:27017>'})],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggingMiddleware)
      .exclude(
        { path: 'products', method: RequestMethod.DELETE }
      )
      .forRoutes(ProductsController)
  }
}

Here, the middleware function does not execute for the DELETE method in the ProductsController.

3 – NestJS Functional Middleware

In the above examples, you have used class-based middleware.

However, it is overkill considering that our class does not have dependencies, member variables or other methods.

In such cases, you can instead opt for functional middleware.

Check the below example:

import { NextFunction, Request, Response } from "express";

export function logger(req: Request, res: Response, next: NextFunction) {
    console.log(`Request object: ${JSON.stringify(req.headers)}`);
    next();
}

The logger() function does the same thing as before but without the class syntax.

Within the app.module.ts, you can register the function.

import { logger } from './middlewares/logging.middleware';

export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(logger)
      .forRoutes(ProductsController)
  }
}

4 – NestJS Global Middleware

There are cases when you want to apply a middleware function to the entire application scope. Logging can be one such requirement.

In such situations, you can apply a middleware function globally by attaching it to the application instance.

See the below snippet from the main.ts file.

import { NestFactory } from '@nestjs/core';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';
import { logger } from './middlewares/logging.middleware';

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

  const swaggerConfig = new DocumentBuilder()
    .setTitle('Products API')
    .setDescription('This is the Products API')
    .setVersion('1.0')
    .addTag('products')
    .build();

  const document = SwaggerModule.createDocument(app, swaggerConfig);
  SwaggerModule.setup('api-docs', app, document);

  await app.listen(3000);
}
bootstrap();

Here, the logger() middleware function is attached to the application instance using the app.use() function.

What’s Next?

The code for this post is available on GitHub. You can download it and play around with it to practice the concepts discussed in this post.

Here’s a summary of what you have learnt in this post:

  • What is a middleware function?
  • How to apply a middleware function in NestJS?
  • Functional middleware
  • Global middleware

In the next post, I’m going to cover the topic of interceptors in NestJS.

In case of any queries or comments, don’t hesitate to write them in the comments section below.


NestJS is a core pillar in my Cloud & Backend learning path. To know more, don’t forget to subscribe to the Progressive Coder newsletter.

Also, say ‘Hi’ on Twitter for more real-time updates on what’s happening at Progressive Coder.

Categories: BlogNestJS

Saurabh Dashora

Saurabh is a Software Architect with over 12 years of experience. He has worked on large-scale distributed systems across various domains and organizations. He is also a passionate Technical Writer and loves sharing knowledge in the community.

6 Comments

Naf · September 21, 2021 at 4:03 am

Thank you very much for your work.

    Saurabh Dashora · September 21, 2021 at 8:19 am

    Glad to know it helped!!

Maryfaith Mgbede · November 29, 2021 at 10:18 pm

Very clear. Thanks for the article

    Saurabh Dashora · December 1, 2021 at 4:03 am

    Glad that the article helped!!

Botcex · December 26, 2021 at 11:14 am

so how can i just use it in a function inside controller?

    Saurabh Dashora · December 28, 2021 at 11:57 am

    You can scope it to a particular controller or function level based on forRoutes(). See below:
    consumer.apply(LoggingMiddleware).forRoutes("/")

Leave a Reply

Your email address will not be published. Required fields are marked *