LearnAI ToolsCareerPractice BuildsPlayDevelopmentContact
Lesson 715 min read

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 You Will Learn
  • 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.
Always Call next()

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.ts
import { 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();
}
}
Console

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() ArgumentApplies To
'products'Every route under the /products path.
{ path: 'products', method: RequestMethod.POST }Only POST /products.
ProductsControllerEvery 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.ts
import helmet from 'helmet';
const app = await NestFactory.create(AppModule);
app.use(helmet());
app.enableCors(); // built-in helper for CORS
Note on app.use()

Middleware 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

ToolKnows Which Handler Runs Next?Best For
MiddlewareNoLow-level, route-agnostic work: logging, headers, CORS, body parsing.
Guards (lesson 9)Yes (via ExecutionContext)Allow/deny decisions: authentication and roles.
Interceptors (lesson 10)YesWrapping the handler: timing, caching, reshaping responses.

Common Mistakes

Avoid These 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.

Next Lesson →

Exception Filters & Error Handling