Photo by PJH on Unsplash

On this page

This chapter follows The NestJS request lifecycle.

Nest middleware is Express middleware with dependency injection added. It receives the request, the response, and a next function, and it either calls next() to pass the request on or ends the response itself. Because it runs before routing, it suits work that applies to whole groups of URLs regardless of which handler answers: tagging a request with an ID, setting security headers, or logging that a request arrived.

Class middleware

A class that implements NestMiddleware can inject providers through its constructor, like any other provider:

src/common/request-id.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { randomUUID } from 'node:crypto';
import type { NextFunction, Request, Response } from 'express';
import { AppLogger } from '../logging/app-logger';
@Injectable()
export class RequestIdMiddleware implements NestMiddleware {
constructor(private readonly logger: AppLogger) {}
use(req: Request, res: Response, next: NextFunction) {
const id = req.header('x-request-id') ?? randomUUID();
req.headers['x-request-id'] = id;
res.setHeader('x-request-id', id);
this.logger.debug(`${req.method} ${req.originalUrl} ${id}`);
next();
}
}

A request that never reaches next() and never gets a response hangs until the client times out. Every path through use() must do one or the other.

Function middleware

When middleware has no dependencies, a plain function is enough:

src/common/no-cache.middleware.ts
import type { NextFunction, Request, Response } from 'express';
export function noCache(req: Request, res: Response, next: NextFunction) {
res.setHeader('Cache-Control', 'no-store');
next();
}

Existing Express middleware packages such as helmet or compression are functions of the same shape and plug in the same way, as long as the app runs on the Express adapter.

Binding middleware to routes

Middleware is not listed in the @Module() decorator. A module implements NestModule and binds middleware in its configure() method through a MiddlewareConsumer:

src/app.module.ts
import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';
import { CatsModule } from './cats/cats.module';
import { noCache } from './common/no-cache.middleware';
import { RequestIdMiddleware } from './common/request-id.middleware';
@Module({ imports: [CatsModule] })
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(RequestIdMiddleware).forRoutes('{*splat}');
consumer
.apply(noCache)
.exclude({ path: 'cats/public', method: RequestMethod.GET })
.forRoutes(CatsController);
}
}

forRoutes() accepts path strings, objects with a path and a method, or controller classes, in which case every route of that controller is covered. apply() takes several middleware at once and runs them in the order given. Since NestJS 11 runs on Express 5, wildcards must be named: 'cats/*splat' matches everything below cats/, and '{*splat}' matches every path including the root. Older code with a bare '*' is converted automatically, with a deprecation warning at startup.

Global middleware

app.use() in main.ts binds middleware to every route before any module-bound middleware runs:

src/main.ts
import helmet from 'helmet';
const app = await NestFactory.create(AppModule);
app.use(helmet());
app.enableCors({ origin: ['https://app.example.com'] });

Middleware registered this way is created outside the injector, so it cannot inject providers. For a global middleware that needs a service, use class middleware with forRoutes('{*splat}') in the root module, as in the example above. enableCors() is a built-in shortcut for the CORS middleware.

What middleware should not do

Middleware has no ExecutionContext. It cannot see which handler will run, which decorators that handler carries, or which arguments it takes. That rules it out for some jobs that look like a good fit at first:

Job Better stage Why
Checking a user’s role Guard Needs the @Roles() metadata on the handler
Measuring handler time or reshaping responses Interceptor Needs to wrap the handler and see its result
Validating the body Pipe Needs the DTO type of the argument
Formatting errors Exception filter Needs to catch what later stages throw

Middleware also only exists for HTTP. A GraphQL resolver behind the same server still passes through it, but a microservice message handler or a WebSocket gateway does not. Guards, interceptors, pipes, and filters work in all of them.

Next: Guards in NestJS.

Check your understanding

Question 1 of 5

0 of 5 checked

  1. 1. What happens when middleware neither calls next() nor ends the response?

Search articles

Type a word to search published articles.

Open the search page