Middleware

The middleware is a function which is called before the route handler. Middleware functions have access to the request and response objects, and the next middleware function in the application’s request-response cycle. The next middleware function is commonly denoted by a variable named next.

The Nest middleware, by default, are equal to express middleware. Here's a great list of the middleware capabilities copied from the official express documentation:

Middleware functions can perform the following tasks:
  • execute any code.
  • make changes to the request and the response objects.
  • end the request-response cycle.
  • call the next middleware function in the stack.
  • if the current middleware function does not end the request-response cycle, it must call next() to pass control to the next middleware function. Otherwise, the request will be left hanging.

The Nest middleware is either a function, or a class with an @Injectable() decorator. The class should implement the NestMiddleware interface, while function does not have any special requirements. Let's start from the LoggerMiddleware example.

logger.middleware.ts
JS TS
TypeScript

import { Injectable, NestMiddleware, MiddlewareFunction } from '@nestjs/common';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  resolve(...args: any[]): MiddlewareFunction {
    return (req, res, next) => {
      console.log('Request...');
      next();
    };
  }
}
TypeScript

import { Injectable } from '@nestjs/common';

@Injectable()
export class LoggerMiddleware {
  resolve(...args) {
    return (req, res, next) => {
      console.log('Request...');
      next();
    };
  }
}

The resolve() method has to return a regular library-specific middleware (req, res, next) => any.

Dependency injection

There is no exception when it comes to the middleware. Same as providers and controllers, they are able to inject dependencies that belongs to the same module (through the constructor).

Applying middleware

There is no place for middleware in the @Module() decorator. We have to set them up using the configure() method of the module class. Modules that include middleware have to implement the NestModule interface. Let's set up the LoggerMiddleware at the ApplicationModule level.

app.module.ts
JS TS
TypeScript

import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middlewares/logger.middleware';
import { CatsModule } from './cats/cats.module';

@Module({
  imports: [CatsModule],
})
export class ApplicationModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('cats');
  }
}
TypeScript

import { Module } from '@nestjs/common';
import { LoggerMiddleware } from './common/middlewares/logger.middleware';
import { CatsModule } from './cats/cats.module';

@Module({
  imports: [CatsModule],
})
export class ApplicationModule {
  configure(consumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('cats');
  }
}

In the above example we have set up the LoggerMiddleware for /cats route handlers that we have previously defined inside the CatsController. Besides, we may restrict a middleware to the particular request method.

app.module.ts
JS TS
TypeScript

import { Module, NestModule, RequestMethod, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middlewares/logger.middleware';
import { CatsModule } from './cats/cats.module';

@Module({
  imports: [CatsModule],
})
export class ApplicationModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes({ path: 'cats', method: RequestMethod.GET });
  }
}
TypeScript

import { Module, RequestMethod } from '@nestjs/common';
import { LoggerMiddleware } from './common/middlewares/logger.middleware';
import { CatsModule } from './cats/cats.module';

@Module({
  imports: [CatsModule],
})
export class ApplicationModule {
  configure(consumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes({ path: 'cats', method: RequestMethod.GET });
  }
}

Route wildcards

Pattern based routes are supported as well. For instance, the asterisk is used as a wildcard, and will match any combination of characters.

TypeScript

forRoutes({ path: 'ab*cd', method: RequestMethod.ALL })

Above route path will match abcd, ab_cd, abecd, and so on. The characters ?, +, *, and () are subsets of their regular expression counterparts. The hyphen ( -) and the dot (.) are interpreted literally by string-based paths.

Middleware consumer

The MiddlewareConsumer is a helper class. It provides several built-in methods to manage middleware. All of them can be simply chained. The forRoutes() can take a single string, multiple strings, RouteInfo object, a controller class and even multiple controller classes. In most cases you'll probably just pass the controllers and separate them by a comma. Below is an example with a single controller:

app.module.ts
JS TS
TypeScript

import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middlewares/logger.middleware';
import { CatsModule } from './cats/cats.module';

@Module({
  imports: [CatsModule],
})
export class ApplicationModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes(CatsController);
  }
}
TypeScript

import { Module } from '@nestjs/common';
import { LoggerMiddleware } from './common/middlewares/logger.middleware';
import { CatsModule } from './cats/cats.module';

@Module({
  imports: [CatsModule],
})
export class ApplicationModule {
  configure(consumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes(CatsController);
  }
}
Hint The apply() method may either take a single middleware, or an array of middleware.

Whilst class is used, quite often we might want to exclude certain routes. That is very intuitive due to the exclude() method.

app.module.ts
JS TS
TypeScript

import { Module, NestModule, RequestMethod, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middlewares/logger.middleware';
import { CatsModule } from './cats/cats.module';

@Module({
  imports: [CatsModule],
})
export class ApplicationModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .exclude(
        { path: 'cats', method: RequestMethod.GET },
        { path: 'cats', method: RequestMethod.POST },
      )
      .forRoutes(CatsController);
  }
}
TypeScript

import { Module, RequestMethod } from '@nestjs/common';
import { LoggerMiddleware } from './common/middlewares/logger.middleware';
import { CatsModule } from './cats/cats.module';

@Module({
  imports: [CatsModule],
})
export class ApplicationModule {
  configure(consumer) {
    consumer
      .apply(LoggerMiddleware)
      .exclude(
        { path: 'cats', method: RequestMethod.GET },
        { path: 'cats', method: RequestMethod.POST },
      )
      .forRoutes(CatsController);
  }
}

Consequently, LoggerMiddleware will be bounded to all routes defined inside CatsController except these two passed to the exclude() function. Please note that exclude() method won't work with your functional middleware. In addition, this function doesn't exclude paths from more generic routes (e.g. wildcards). In such case, you should rather put your paths-restriction logic directly to the middleware and, for example, compare a request's URL.

Configurable middleware

Sometimes the behaviour of the middleware depends on the custom values e.g. an array of user roles, options object, and so on. We may apply additional arguments to the resolve() using the with() method. See an example below:

app.module.ts
JS TS
TypeScript

import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middlewares/logger.middleware';
import { CatsModule } from './cats/cats.module';
import { CatsController } from './cats/cats.controller';

@Module({
  imports: [CatsModule],
})
export class ApplicationModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .with('ApplicationModule')
      .forRoutes(CatsController);
  }
}
TypeScript

import { Module } from '@nestjs/common';
import { LoggerMiddleware } from './common/middlewares/logger.middleware';
import { CatsModule } from './cats/cats.module';
import { CatsController } from './cats/cats.controller';

@Module({
  imports: [CatsModule],
})
export class ApplicationModule {
  configure(consumer) {
    consumer
      .apply(LoggerMiddleware)
      .with('ApplicationModule')
      .forRoutes(CatsController);
  }
}

We have passed a plain string - ApplicationModule to the with() method. Thereafter, we have to adjust the resolve() method of the LoggerMiddleware.

logger.middleware.ts
JS TS
TypeScript

import { Injectable, NestMiddleware, MiddlewareFunction } from '@nestjs/common';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  resolve(name: string): MiddlewareFunction {
    return (req, res, next) => {
      console.log(`[${name}] Request...`); // [ApplicationModule] Request...
      next();
    };
 }
}
TypeScript

import { Injectable } from '@nestjs/common';

@Injectable()
export class LoggerMiddleware {
  resolve(name) {
    return (req, res, next) => {
      console.log(`[${name}] Request...`); // [ApplicationModule] Request...
      next();
    };
 }
}

In this case, the value of the name property will be 'ApplicationModule'.

Asynchronous middleware

There are no contraindications that would prevent us from returning the async function within the resolve() method. Also, it's possible to make the resolve() method async as well. This common pattern is called deffered middleware.

logger.middleware.ts
JS TS
TypeScript

import { Injectable, NestMiddleware, MiddlewareFunction } from '@nestjs/common';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  async resolve(name: string): Promise<MiddlewareFunction> {
    await someAsyncJob();

    return async (req, res, next) => {
      await someAsyncJob();
      console.log(`[${name}] Request...`); // [ApplicationModule] Request...
      next();
    };
 }
}
TypeScript

import { Injectable } from '@nestjs/common';

@Injectable()
export class LoggerMiddleware {
  async resolve(name) {
    await someAsyncJob();

    return async (req, res, next) => {
      await someAsyncJob();
      console.log(`[${name}] Request...`); // [ApplicationModule] Request...
      next();
    };
  }
}

Functional middleware

The LoggerMiddleware is quite short. It has no members, no additional methods, no dependencies. Why can't we just use a simple function? It's a good question, cause in fact - we can. This type of the middleware is called functional middleware . Let's transform the logger into a function.

logger.middleware.ts
JS TS
TypeScript

export function logger(req, res, next) {
  console.log(`Request...`);
  next();
};

And use it within the ApplicationModule:

app.module.ts
JS TS
TypeScript

import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { logger } from './common/middlewares/logger.middleware';
import { CatsModule } from './cats/cats.module';
import { CatsController } from './cats/cats.controller';

@Module({
  imports: [CatsModule],
})
export class ApplicationModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(logger)
      .forRoutes(CatsController);
  }
}
TypeScript

import { Module } from '@nestjs/common';
import { logger } from './common/middlewares/logger.middleware';
import { CatsModule } from './cats/cats.module';
import { CatsController } from './cats/cats.controller';

@Module({
  imports: [CatsModule],
})
export class ApplicationModule {
  configure(consumer) {
    consumer
      .apply(logger)
      .forRoutes(CatsController);
  }
}
Hint Let's consider using functional middleware every time when your middleware doesn't need any dependencies.

Multiple middleware

As mentioned before, in order to bind multiple middleware that are executed sequentially, we can separate them by a comma inside the apply() method.

JS TS
TypeScript

export class ApplicationModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(cors(), helmet(), logger)
      .forRoutes(CatsController);
  }
}
TypeScript

@Module({
  imports: [CatsModule],
})
export class ApplicationModule {
  configure(consumer) {
      consumer
        .apply(cors(), helmet(), logger)
        .forRoutes(CatsController);
    }
  }

Global middleware

In order to tie a middleware to each registered route at once, we can take advantage of use() method that is supplied by the INestApplication instance:

TypeScript

const app = await NestFactory.create(ApplicationModule);
app.use(logger);
await app.listen(3000);
  • Middleware
  • Dependency injection
  • Applying middleware
  • Route wildcards
  • Middleware consumer
  • Configurable middleware
  • Asynchronous middleware
  • Functional middleware
  • Multiple middleware
  • Global middleware

Support us

Nest is an MIT-licensed open source project. It can grow thanks to the support by these awesome people. If you'd like to join them, please read more here.

Principal Sponsor

Sponsors / Backers