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:
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:
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:
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:
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