NestJS Logo
ads via Carbon Your agent. Your model. Your rules. Try it free ads via Carbon

Middleware

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

By default, Nest middleware is equivalent to Express middleware. The official Express documentation describes the capabilities of middleware as follows:

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.

You implement custom Nest middleware either as a function or as a class with the @Injectable() decorator. A class should implement the NestMiddleware interface, while a function has no special requirements. Let's start by implementing a simple middleware class.

Warning Express and Fastify handle middleware differently and provide different method signatures. See the Performance (Fastify) chapter for details.
logger.middleware.ts
JS TS

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

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log('Request...');
    next();
  }
}

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

@Injectable()
export class LoggerMiddleware {
  use(req, res, next) {
    console.log('Request...');
    next();
  }
}

Dependency injection#

Nest middleware fully supports dependency injection. Like providers and controllers, middleware classes can inject dependencies that are available within the same module. As usual, dependencies are injected through the constructor.

Applying middleware#

Middleware is not registered in the @Module() decorator. Instead, you set it up in the configure() method of the module class. Modules that include middleware must implement the NestModule interface. Let's set up the LoggerMiddleware at the AppModule level.

app.module.ts
JS TS

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

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

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

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

In the example above, the LoggerMiddleware is applied to the /cats route handlers defined in the CatsController. To restrict middleware to a particular request method, pass an object containing the route path and the request method to the forRoutes() method. The example below imports the RequestMethod enum to reference the desired request method.

app.module.ts
JS TS

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

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

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

@Module({
  imports: [CatsModule],
})
export class AppModule {
  configure(consumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes({ path: 'cats', method: RequestMethod.GET });
  }
}
Hint The configure() method can be asynchronous. Declare it with async to await the completion of an asynchronous operation inside the method body.
Warning With the Express adapter, Nest registers the json and urlencoded body parsers (express.json() and express.urlencoded()) by default. To customize these parsers through the MiddlewareConsumer, disable the defaults by setting the bodyParser option to false when creating the application with NestFactory.create().

Route wildcards#

Middleware also supports pattern-based routes. For example, the named wildcard (*splat) matches any combination of characters in a route. In the following example, the middleware runs for any route that starts with abcd/, regardless of how many characters follow.


forRoutes({
  path: 'abcd/*splat',
  method: RequestMethod.ALL,
});
Hintsplat is only the name of the wildcard parameter and has no special meaning. You can use any name, e.g., *wildcard.

The 'abcd/*splat' route path matches abcd/1, abcd/123, abcd/abc, and so on. String-based paths interpret the hyphen (-) and the dot (.) literally. However, abcd/ with no additional characters does not match. To match it as well, wrap the wildcard in braces to make it optional:


forRoutes({
  path: 'abcd/{*splat}',
  method: RequestMethod.ALL,
});

Middleware consumer#

The MiddlewareConsumer is a helper class that provides several built-in methods to manage middleware. All of them can be chained in the fluent style. The forRoutes() method accepts a single string, multiple strings, a RouteInfo object, a controller class, or multiple controller classes. In most cases, you'll pass a comma-separated list of controllers. Below is an example with a single controller:

app.module.ts
JS TS

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

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

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

@Module({
  imports: [CatsModule],
})
export class AppModule {
  configure(consumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes(CatsController);
  }
}
Hint The apply() method accepts either a single middleware or multiple arguments to specify multiple middleware.

Excluding routes#

To exclude certain routes from having middleware applied, use the exclude() method. It accepts a single string, multiple strings, or a RouteInfo object that identifies the routes to exclude:


consumer
  .apply(LoggerMiddleware)
  .exclude(
    { path: 'cats', method: RequestMethod.GET },
    { path: 'cats', method: RequestMethod.POST },
    'cats/{*splat}',
  )
  .forRoutes(CatsController);

With the example above, LoggerMiddleware is bound to all routes defined inside CatsControllerexcept those matching the three entries passed to the exclude() method.

Hint The exclude() method supports wildcard parameters using the path-to-regexp package.

Functional middleware#

The LoggerMiddleware class we've been using is minimal: it has no members, no additional methods, and no dependencies. Middleware like this can be defined as a plain function instead of a class. This type of middleware is called functional middleware. Let's convert the logger middleware from a class into a function to illustrate the difference:

logger.middleware.ts
JS TS

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

export function logger(req: Request, res: Response, next: NextFunction) {
  console.log('Request...');
  next();
}

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

Then use it within the AppModule:

app.module.ts
JS TS

consumer
  .apply(logger)
  .forRoutes(CatsController);
Hint Consider using functional middleware whenever your middleware doesn't need any dependencies.

Multiple middleware#

To bind multiple middleware that execute sequentially, pass a comma-separated list to the apply() method:


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

Global middleware#

To bind middleware to every registered route at once, use the use() method of the INestApplication instance:

main.ts
JS TS

const app = await NestFactory.create(AppModule);
app.use(logger);
await app.listen(process.env.PORT ?? 3000);
Hint Global middleware registered with app.use() cannot access the DI container, so use functional middleware there. Alternatively, use a class middleware and bind it with .forRoutes('*') within the AppModule (or any other module).

Error handling#

When middleware throws an exception, Nest's exceptions layer catches it and sends an appropriate response, just as it does for exceptions thrown from a route handler. The recommended approach is to throw an HttpException (or a built-in subclass such as UnauthorizedException):

auth.middleware.ts
JS TS

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

@Injectable()
export class AuthMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    if (!req.headers.authorization) {
      throw new UnauthorizedException();
    }
    next();
  }
}

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

@Injectable()
export class AuthMiddleware {
  use(req, res, next) {
    if (!req.headers.authorization) {
      throw new UnauthorizedException();
    }
    next();
  }
}

If the middleware is asynchronous, declare use() as async (or return a Promise) so that a rejected promise is forwarded to the exceptions layer:

auth.middleware.ts
JS TS

@Injectable()
export class AuthMiddleware implements NestMiddleware {
  constructor(private readonly authService: AuthService) {}

  async use(req: Request, res: Response, next: NextFunction) {
    const user = await this.authService.verify(req.headers.authorization);
    if (!user) {
      throw new UnauthorizedException();
    }
    req['user'] = user;
    next();
  }
}

You can also pass the error to next(). This is useful when wrapping existing Express-style middleware that reports failures through the callback:


use(req: Request, res: Response, next: NextFunction) {
  if (!req.headers.authorization) {
    return next(new UnauthorizedException());
  }
  next();
}
Warning Because middleware runs before a route handler is selected, only global exception filters (registered with app.useGlobalFilters() or the APP_FILTER token) catch exceptions thrown from middleware. Method-scoped and controller-scoped filters are not invoked, and binding filters to a middleware class with @UseFilters() is not supported.
Hint Middleware registered with app.use() is handled by the underlying HTTP platform (Express or Fastify), not by Nest's MiddlewareModule. Prefer throwing errors (or calling next(err)) from middleware bound with the MiddlewareConsumer, so that the exceptions layer can process them.
Edit on GitHub

Support us

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

Principal Sponsors

SerpApi LogoTrilon LogoMojam Logo

Sponsors / Partners

Become a sponsor