NestJS Middleware
Middleware is a function that runs between the incoming HTTP request and the route handler. It receives the request and response objects, performs some work — logging, validating a header, modifying the request — and then either passes control to the next function in the chain or terminates the request by sending a response.
The Middleware Pipeline
HTTP Request
|
v
Middleware 1 (Logger)
|
v
Middleware 2 (Auth token check)
|
v
Route Handler (Controller method)
|
v
HTTP Response
Each middleware calls next() to pass control forward. If a middleware does not call next(), the request stops there and no further processing happens.
Creating Middleware
Middleware is a class decorated with @Injectable() that implements the NestMiddleware interface:
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) {
const { method, originalUrl } = req;
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`[${method}] ${originalUrl} - ${res.statusCode} (${duration}ms)`);
});
next(); // must call this to continue the request
}
}
Registering Middleware
Unlike guards and interceptors, middleware registers in the module class using the configure() method — not through a decorator:
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
@Module({
controllers: [UsersController],
providers: [UsersService],
})
export class UsersModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes('users'); // applies to all /users routes
}
}
Targeting Specific Routes
// Apply to all routes in UsersController
consumer.apply(LoggerMiddleware).forRoutes(UsersController);
// Apply only to GET /users
consumer
.apply(LoggerMiddleware)
.forRoutes({ path: 'users', method: RequestMethod.GET });
// Apply to everything except a specific route
consumer
.apply(AuthMiddleware)
.exclude({ path: 'users/register', method: RequestMethod.POST })
.forRoutes(UsersController);
Functional Middleware
For simple middleware without dependencies, a plain function works and avoids creating a class:
export function logger(req: Request, res: Response, next: NextFunction) {
console.log(`Request... ${req.method} ${req.url}`);
next();
}
// Register it the same way
consumer.apply(logger).forRoutes('*');
Global Middleware
Apply middleware globally across the entire application in main.ts:
const app = await NestFactory.create(AppModule); app.use(logger); // function middleware await app.listen(3000);
Global middleware applies to every route. Use it for application-wide concerns like request logging, CORS headers, or body size limits.
Middleware vs Guards
Middleware Guard ───────────────────────────── ───────────────────────────── Runs before route matching Runs after route matching No access to route metadata Has access to route metadata Can call next() or end request Returns true (allow) or false (block) Good for: logging, parsing Good for: auth, permissions Registered via configure() Applied via @UseGuards() decorator
A logging middleware that records every request regardless of outcome is a classic middleware use case. A guard that checks whether the user is logged in before allowing access to a protected route is a classic guard use case. Both exist in the pipeline, but they operate at different points and with different capabilities.
Request Modification Example
Middleware can attach data to the request object for downstream handlers to use:
@Injectable()
export class RequestIdMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
req['requestId'] = crypto.randomUUID(); // attach a unique ID
next();
}
}
// In a controller, access the attached value
@Get()
findAll(@Req() req: Request) {
const requestId = req['requestId'];
this.logger.log(`Handling request ${requestId}`);
return this.usersService.findAll();
}
Middleware sits at the outermost edge of your request pipeline. It runs before routing, before guards, before interceptors, and before pipes. Use it for cross-cutting concerns that apply broadly — logging every request, setting security headers, reading and attaching session data, or enforcing rate limits at the network level before any application logic runs.
