Middleware
Run code before your route handlers with NestJS middleware: class and functional middleware, applying it to specific routes, and using Express middleware like helmet.
On this page 12 sections
Introduction
Middleware is code that runs before the route handler, with access to the raw request and response objects. If you have used Express, you already know the idea — and Nest middleware is, by default, equivalent to Express middleware.
- What middleware can do and when it runs.
- How to write class-based and functional middleware.
- How to apply middleware to specific routes with MiddlewareConsumer.
- How to register global middleware and existing Express packages.
- When to choose middleware over guards or interceptors.
What is Middleware?
A middleware function receives the request, the response and a next() function. It can:
- Run any code (logging, timing, adding a request ID).
- Modify the request or response objects.
- End the request-response cycle early (for example, block a request).
- Call next() to pass control to the next middleware or the route handler.
If middleware neither ends the response nor calls next(), the request hangs forever until the client times out.
Class-Based Middleware
// src/common/middleware/logger.middleware.tsimport { Injectable, NestMiddleware } from '@nestjs/common';import { NextFunction, Request, Response } from 'express';
@Injectable()export class LoggerMiddleware implements NestMiddleware { use(req: Request, res: Response, next: NextFunction) { const start = Date.now(); res.on('finish', () => { console.log(`${req.method} ${req.originalUrl} ${res.statusCode} - ${Date.now() - start}ms`); }); next(); }}Click Run to see what this code prints.
Because class middleware is @Injectable(), it can inject other providers through its constructor, just like a service.
Applying Middleware
Middleware is not listed in @Module(). Instead, the module implements NestModule and configures it in a configure() method:
import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common';import { LoggerMiddleware } from './common/middleware/logger.middleware.js';import { ProductsModule } from './products/products.module.js';import { ProductsController } from './products/products.controller.js';
@Module({ imports: [ProductsModule],})export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware) .exclude({ path: 'health', method: RequestMethod.GET }) .forRoutes(ProductsController); }}| forRoutes() Argument | Applies To |
|---|---|
| 'products' | Every route under the /products path. |
| { path: 'products', method: RequestMethod.POST } | Only POST /products. |
| ProductsController | Every route defined in that controller. |
| '{*splat}' (written '*' before Nest 11) | Every route in the app. |
Functional Middleware
If the middleware has no dependencies, a plain function is simpler:
import { NextFunction, Request, Response } from 'express';
export function requestId(req: Request, res: Response, next: NextFunction) { res.setHeader('X-Request-Id', crypto.randomUUID()); next();}
// in configure():consumer.apply(requestId, LoggerMiddleware).forRoutes('{*splat}');Global Middleware
To apply middleware to every route, or to use an existing Express middleware package, call app.use() in main.ts. A common example is helmet, which sets security-related HTTP headers:
npm install helmet// src/main.tsimport helmet from 'helmet';
const app = await NestFactory.create(AppModule);app.use(helmet());app.enableCors(); // built-in helper for CORSMiddleware registered with app.use() lives outside Nest's DI container, so it cannot inject providers. If your global middleware needs a service, use consumer.apply(...).forRoutes('{*splat}') in AppModule instead.
Middleware vs Guards vs Interceptors
| Tool | Knows Which Handler Runs Next? | Best For |
|---|---|---|
| Middleware | No | Low-level, route-agnostic work: logging, headers, CORS, body parsing. |
| Guards (lesson 9) | Yes (via ExecutionContext) | Allow/deny decisions: authentication and roles. |
| Interceptors (lesson 10) | Yes | Wrapping the handler: timing, caching, reshaping responses. |
Common Mistakes
- Forgetting to call next(), leaving requests hanging.
- Trying to register middleware in the providers array — use configure() with MiddlewareConsumer.
- Doing authentication in middleware — guards are designed for that and know which handler (and its metadata) will run.
- Expecting app.use() middleware to inject services.
Best Practices
- Use middleware for cross-cutting, route-agnostic concerns only.
- Prefer functional middleware when no dependencies are needed.
- Use helmet() and enableCors() in main.ts for basic HTTP hardening.
- Use exclude() to keep noisy endpoints like health checks out of logs.
Frequently Asked Questions
Yes, when Nest runs on Express (the default). With the Fastify adapter you need Fastify-compatible plugins instead.
Global middleware (app.use) runs first, then module-bound middleware, in the order you pass them to apply().
Key Takeaways
- Middleware runs before route handlers and has access to req, res and next().
- Class middleware implements NestMiddleware and can inject providers.
- Modules apply middleware via configure(consumer) with forRoutes() and exclude().
- app.use() registers global middleware, including Express packages like helmet.
- Use guards for auth and interceptors for wrapping handlers, not middleware.
Summary
You can now run code before any route in your app. Next, you will handle the other end of the request: what happens when something goes wrong, using exceptions and exception filters.