NestJS Middleware Tutorial – Complete Guide with Examples
Middleware is one of the oldest and most familiar concepts in Node.js web development, and NestJS embraces it fully while adding its own structured way of applying it. If you have ever used Express middleware, NestJS's version will feel immediately familiar, with a few additional conventions layered on top for consistency with the rest of the framework's architecture.
This lesson explains what middleware actually does, the two ways to write it in NestJS, functional and class-based, and precisely how to control which routes a given piece of middleware applies to using the MiddlewareConsumer API.
NestJS Middleware: Learning Objectives
- Define what middleware is and where it runs relative to guards, pipes, and interceptors.
- Write a simple functional middleware for logging requests.
- Build a class-based middleware implementing the NestMiddleware interface.
- Apply middleware to specific routes using MiddlewareConsumer inside a module.
- Understand common real-world use cases for middleware.
NestJS Middleware: Key Terms and Definitions
- Middleware: A function or class that has access to the request and response objects and the next() function, executed before a route's actual handler.
- next() function: A function that, when called inside middleware, passes control to the next middleware or the route handler itself.
- Functional middleware: Middleware written as a simple function rather than a class, suitable for simple, stateless logic.
- Class-based middleware: Middleware implemented as a class that implements the NestMiddleware interface, useful when dependency injection is needed.
- MiddlewareConsumer: An interface used inside a module's configure() method to apply middleware to specific routes or controllers.
How NestJS Middleware Works: Detailed Explanation
Middleware in NestJS runs at the very earliest stage of the request lifecycle, even before guards, pipes, or interceptors get involved. Functionally, it works exactly like Express middleware, because NestJS's default HTTP adapter is Express: a middleware function receives the request object, the response object, and a next() function, and it can inspect or modify the request, terminate the request-response cycle early (by sending a response directly), or call next() to pass control forward to whatever comes next, whether that's another middleware or the eventual route handler.
NestJS supports two styles of middleware. Functional middleware is simply a plain function following the (req, res, next) signature, ideal for lightweight, stateless tasks like basic request logging. Class-based middleware, by contrast, is a class marked with @Injectable() that implements the NestMiddleware interface, requiring a use(req, res, next) method. The class-based approach becomes valuable when your middleware needs to leverage NestJS's dependency injection system, for example injecting a logging service or a configuration service that the middleware relies on.
Unlike guards, pipes, and interceptors, which are typically applied using decorators directly on controllers or methods, middleware in NestJS is configured differently: inside a module's configure() method, which requires the module class to implement the NestModule interface. Within configure(), you receive a MiddlewareConsumer instance, which provides a fluent API to specify exactly which middleware applies to which routes, using .apply(YourMiddleware).forRoutes(SomeController) or an even more specific path string and method combination, giving you fine-grained control comparable to Express's own app.use() patterns but organized consistently within NestJS's module system.
Common real-world use cases for middleware include request logging (recording the method, path, and response time of every request), setting common response headers, basic request ID generation for tracing, and simple authentication token extraction before more sophisticated guard-based authorization logic runs later in the pipeline. Middleware is generally the right tool when you need to affect the raw request or response objects broadly and early, before NestJS's more specialized mechanisms like guards and pipes take over.
Interview-Friendly Explanation
A strong interview or viva answer for nestjs middleware must go beyond a one-line definition. Interviewers evaluating BCA, MCA, B.Tech, and self-taught backend developers reward answers that combine a precise definition, a clear working explanation, a real-world example, and a conclusion that highlights why it matters in production Node.js applications. Use the four-point framework below when answering under time pressure.
- Definition point: Middleware: A function or class that has access to the request and response objects and the next() function, executed before a route's actual handler.
- Working point: Write a simple functional middleware for logging requests.
- Example point: Nearly every production API uses some form of request logging middleware to record method, path, status code, and response time for monitoring and debugging purposes.
- Conclusion point: Middleware is the right tool when you need to affect the raw request or response early, before NestJS's more specialized request-handling layers engage.
How to Answer This in a Technical Interview
- Give a two-to-three sentence definition using correct NestJS and Node.js terminology.
- Add one specific example drawn from a real backend scenario such as an e-commerce, fintech, or SaaS API.
- Mention the request lifecycle stage this concept operates at (pipe, guard, middleware, filter, interceptor) since interviewers often test whether you know the execution order.
- Close with one benefit, trade-off, or production use case.
- Avoid vague answers like "it just validates stuff" — interviewers filter these out immediately.
Practical Scenario
Imagine you are hardening a production API for a SaaS product, similar to systems used at companies like Razorpay, Freshworks, or Postman. Every lesson in this module maps directly to a decision you will make while making that backend secure, predictable, and resilient to bad input: validating what comes in, transforming it safely, catching failures gracefully, and protecting routes from unauthorized access.
NestJS Middleware: Architecture and Flow Diagram
Visualize the very beginning of the request lifecycle:
[Incoming HTTP Request] --> [Middleware (functional or class-based)] --> next() --> [Guards] --> [Interceptors] --> [Pipes] --> [Route Handler]
Add a note: 'Middleware is the earliest customizable stage, running even before NestJS's own guards and pipes.'
Functional Middleware vs Class-Based Middleware: Comparison Table
| Aspect | Functional Middleware | Class-Based Middleware |
|---|---|---|
| Definition style | Plain function (req, res, next) | Class implementing NestMiddleware with a use() method |
| Dependency injection | Not supported | Fully supported via constructor injection |
| Best for | Simple, stateless logic | Logic requiring injected services or configuration |
| Registration | Same MiddlewareConsumer API | Same MiddlewareConsumer API |
NestJS Middleware: NestJS Code Example
// Functional middleware — simple request logger
import { Request, Response, NextFunction } from 'express';
export function requestLogger(req: Request, res: Response, next: NextFunction) {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.originalUrl} - ${res.statusCode} (${duration}ms)`);
});
next();
}
// Class-based middleware — uses dependency injection
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class RequestIdMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
(req as any).requestId = `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
next();
}
}
// app.module.ts — applying both middleware using MiddlewareConsumer
import { Module, NestModule, MiddlewareConsumer, RequestMethod } from '@nestjs/common';
import { UsersController } from './users/users.controller';
import { RequestIdMiddleware } from './common/middleware/request-id.middleware';
import { requestLogger } from './common/middleware/request-logger.middleware';
@Module({
controllers: [UsersController],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(requestLogger, RequestIdMiddleware)
.forRoutes({ path: 'users*', method: RequestMethod.ALL });
}
}
requestLogger is a simple functional middleware that logs every request's method, URL, status code, and duration once the response finishes, requiring no dependency injection. RequestIdMiddleware is a class-based middleware that attaches a unique request ID to every incoming request object, demonstrating the @Injectable() and NestMiddleware pattern. Inside AppModule, implementing NestModule and its required configure() method, both middleware are applied together using consumer.apply(...).forRoutes(...), scoped specifically to any route starting with 'users', for all HTTP methods, showing how MiddlewareConsumer gives precise control over exactly where each middleware runs.
Real-World NestJS Middleware Industry Examples
- Nearly every production API uses some form of request logging middleware to record method, path, status code, and response time for monitoring and debugging purposes.
- Distributed systems commonly use request ID middleware, similar to the example shown, to trace a single request across multiple microservices for debugging complex, multi-service failures.
- Rate limiting is frequently implemented as middleware, inspecting the request's origin or API key early in the pipeline before any business logic or even authentication guards run.
- CORS (Cross-Origin Resource Sharing) handling, whether through NestJS's built-in enableCors() or custom middleware, operates at this same early middleware stage to control which origins can access an API.
NestJS Middleware Interview Questions and Answers
Q1. What is middleware in NestJS and when does it run relative to guards and pipes?
Short answer: Middleware is a function or class with access to the request, response, and next() function, executed at the very earliest stage of the request lifecycle, before guards, interceptors, and pipes run. It can inspect or modify the request, end the request-response cycle early, or call next() to continue processing.
Detailed explanation: Middleware in NestJS runs at the very earliest stage of the request lifecycle, even before guards, pipes, or interceptors get involved. Functionally, it works exactly like Express middleware, because NestJS's default HTTP adapter is Express: a middleware function receives the request object, the response object, and a next() function, and it can inspect or modify the request, terminate the request-response cycle early (by sending a response directly), or call next() to pass control forward to whatever comes next, whether that's another middleware or the eventual route handler. NestJS supports two styles of middleware. Functional middleware is simply a plain function following the (req, res, next) signature, ideal for lightweight, stateless tasks like basic request logging. Class-based middleware, by contrast, is a class marked with @Injectable() that implements the NestMiddleware interface, requiring a use(req, res, next) method. The class-based approach becomes valuable when your middleware needs to leverage NestJS's dependency injection system, for example injecting a logging service or a configuration service that the middleware relies on. Unlike guards, pipes, and interceptors, which are typically applied using decorators directly on controllers or methods, middleware in NestJS is configured differently: inside a module's configure() method, which requires the module class to implement the NestModule interface. Within configure(), you receive a MiddlewareConsumer instance, which provides a fluent API to specify exactly which middleware applies to which routes, using .apply(YourMiddleware).forRoutes(SomeController) or an even more specific path string and method combination, giving you fine-grained control comparable to Express's own app.use() patterns but organized consistently within NestJS's module system. Common real-world use cases for middleware include request logging (recording the method, path, and response time of every request), setting common response headers, basic request ID generation for tracing, and simple authentication token extraction before more sophisticated guard-based authorization logic runs later in the pipeline. Middleware is generally the right tool when you need to affect the raw request or response objects broadly and early, before NestJS's more specialized mechanisms like guards and pipes take over.
Practical example: Nearly every production API uses some form of request logging middleware to record method, path, status code, and response time for monitoring and debugging purposes.
Interview tip: Middleware runs earliest in the request lifecycle: before guards, interceptors, and pipes.
Revision hook: Middleware is the right tool when you need to affect the raw request or response early, before NestJS's more specialized request-handling layers engage.
Q2. What is the difference between functional and class-based middleware in NestJS?
Short answer: Functional middleware is a plain function following the (req, res, next) signature, suitable for simple, stateless logic with no dependency injection needs. Class-based middleware is a class marked @Injectable() implementing the NestMiddleware interface with a use() method, allowing it to leverage NestJS's dependency injection for more complex logic.
Detailed explanation: requestLogger is a simple functional middleware that logs every request's method, URL, status code, and duration once the response finishes, requiring no dependency injection. RequestIdMiddleware is a class-based middleware that attaches a unique request ID to every incoming request object, demonstrating the @Injectable() and NestMiddleware pattern. Inside AppModule, implementing NestModule and its required configure() method, both middleware are applied together using consumer.apply(...).forRoutes(...), scoped specifically to any route starting with 'users', for all HTTP methods, showing how MiddlewareConsumer gives precise control over exactly where each middleware runs.
Practical example: Distributed systems commonly use request ID middleware, similar to the example shown, to trace a single request across multiple microservices for debugging complex, multi-service failures.
Interview tip: Functional middleware: plain (req, res, next) function, no dependency injection.
Revision hook: Choose class-based middleware whenever your logic needs access to other injected providers; otherwise, functional middleware is simpler.
Q3. How do you apply middleware to specific routes in NestJS?
Short answer: Middleware is configured inside a module's configure() method, which requires the module class to implement the NestModule interface. Using the injected MiddlewareConsumer, you call .apply(YourMiddleware).forRoutes(...), specifying either a controller, a path string, or a path-and-method combination to scope exactly where the middleware applies.
Detailed explanation: Middleware in NestJS runs at the earliest stage of the request lifecycle, before guards, interceptors, and pipes, giving it direct access to the raw request and response objects along with a next() function to continue the pipeline. NestJS supports both functional middleware, simple functions ideal for stateless logic like logging, and class-based middleware, which implements the NestMiddleware interface and supports full dependency injection for more complex needs. Unlike other NestJS building blocks applied through decorators, middleware is configured inside a module's configure() method using the MiddlewareConsumer API, allowing precise control over which routes or controllers each middleware applies to. Common real-world uses include request logging, request ID tagging for distributed tracing, and early security checks like CORS handling.
Practical example: Rate limiting is frequently implemented as middleware, inspecting the request's origin or API key early in the pipeline before any business logic or even authentication guards run.
Interview tip: Class-based middleware: @Injectable() class implementing NestMiddleware with a use() method, supports dependency injection.
Revision hook: MiddlewareConsumer's forRoutes() and exclude() methods give you precise, flexible control over exactly where middleware applies.
Q4. Give an example of a common real-world use case for middleware.
Short answer: Request logging is a classic middleware use case: recording the HTTP method, URL, response status code, and processing duration for every incoming request, which is valuable for monitoring, debugging, and performance analysis across an entire application without duplicating this logic in every controller.
Detailed explanation: Middleware in NestJS runs at the very earliest stage of the request lifecycle, even before guards, pipes, or interceptors get involved. Functionally, it works exactly like Express middleware, because NestJS's default HTTP adapter is Express: a middleware function receives the request object, the response object, and a next() function, and it can inspect or modify the request, terminate the request-response cycle early (by sending a response directly), or call next() to pass control forward to whatever comes next, whether that's another middleware or the eventual route handler. NestJS supports two styles of middleware. Functional middleware is simply a plain function following the (req, res, next) signature, ideal for lightweight, stateless tasks like basic request logging. Class-based middleware, by contrast, is a class marked with @Injectable() that implements the NestMiddleware interface, requiring a use(req, res, next) method. The class-based approach becomes valuable when your middleware needs to leverage NestJS's dependency injection system, for example injecting a logging service or a configuration service that the middleware relies on. Unlike guards, pipes, and interceptors, which are typically applied using decorators directly on controllers or methods, middleware in NestJS is configured differently: inside a module's configure() method, which requires the module class to implement the NestModule interface. Within configure(), you receive a MiddlewareConsumer instance, which provides a fluent API to specify exactly which middleware applies to which routes, using .apply(YourMiddleware).forRoutes(SomeController) or an even more specific path string and method combination, giving you fine-grained control comparable to Express's own app.use() patterns but organized consistently within NestJS's module system. Common real-world use cases for middleware include request logging (recording the method, path, and response time of every request), setting common response headers, basic request ID generation for tracing, and simple authentication token extraction before more sophisticated guard-based authorization logic runs later in the pipeline. Middleware is generally the right tool when you need to affect the raw request or response objects broadly and early, before NestJS's more specialized mechanisms like guards and pipes take over.
Practical example: CORS (Cross-Origin Resource Sharing) handling, whether through NestJS's built-in enableCors() or custom middleware, operates at this same early middleware stage to control which origins can access an API.
Interview tip: Middleware is applied via a module's configure(consumer: MiddlewareConsumer) method, not via decorators on controllers.
Revision hook: Common, battle-tested middleware use cases include logging, request ID tagging, and early-stage security checks like CORS or rate limiting.
NestJS Middleware MCQs and Practice Questions
1. Which NestJS request-lifecycle component runs earliest, before guards and pipes?
- Interceptors
- Pipes
- Middleware
- Exception filters
Answer: C. Middleware
Explanation: Middleware executes at the very earliest stage of NestJS's request lifecycle, running before guards, interceptors, and pipes have a chance to process the request.
Concept link: Middleware runs earliest in the request lifecycle: before guards, interceptors, and pipes.
Why this matters: Middleware is the right tool when you need to affect the raw request or response early, before NestJS's more specialized request-handling layers engage.
2. What interface must a class implement to function as class-based middleware?
- PipeTransform
- CanActivate
- NestMiddleware
- ExceptionFilter
Answer: C. NestMiddleware
Explanation: A class-based middleware must implement the NestMiddleware interface, which requires a use(req, res, next) method, and should be marked with @Injectable() to support dependency injection.
Concept link: Functional middleware: plain (req, res, next) function, no dependency injection.
Why this matters: Choose class-based middleware whenever your logic needs access to other injected providers; otherwise, functional middleware is simpler.
3. Where is middleware configured and applied to specific routes in NestJS?
- Inside a controller using @UseMiddleware()
- Inside a module's configure() method using MiddlewareConsumer
- Inside main.ts using app.useMiddleware()
- Middleware cannot be scoped to specific routes
Answer: B. Inside a module's configure() method using MiddlewareConsumer
Explanation: NestJS applies middleware through a module implementing the NestModule interface, using its configure() method and the injected MiddlewareConsumer to specify exactly which routes the middleware should run for.
Concept link: Class-based middleware: @Injectable() class implementing NestMiddleware with a use() method, supports dependency injection.
Why this matters: MiddlewareConsumer's forRoutes() and exclude() methods give you precise, flexible control over exactly where middleware applies.
4. What must middleware call to pass control to the next step in the request pipeline?
- return()
- resolve()
- next()
- forward()
Answer: C. next()
Explanation: Calling next() inside middleware passes control forward to the next registered middleware, or to the route handler if no further middleware remains, continuing the request lifecycle.
Concept link: Middleware is applied via a module's configure(consumer: MiddlewareConsumer) method, not via decorators on controllers.
Why this matters: Common, battle-tested middleware use cases include logging, request ID tagging, and early-stage security checks like CORS or rate limiting.
Common NestJS Middleware Mistakes to Avoid
- Forgetting to call next() inside middleware, causing the request to hang indefinitely since control is never passed forward.
- Assuming middleware can use decorators like @Param() or @Body() the way controllers can; middleware works directly with the raw Express request and response objects instead.
- Trying to apply middleware using a decorator directly on a controller, when NestJS requires middleware to be configured through a module's configure() method instead.
- Using functional middleware when dependency injection is actually needed, then struggling to access injected services that only class-based middleware supports.
NestJS Middleware: Interview Notes and Exam Tips
- Middleware runs earliest in the request lifecycle: before guards, interceptors, and pipes.
- Functional middleware: plain (req, res, next) function, no dependency injection.
- Class-based middleware: @Injectable() class implementing NestMiddleware with a use() method, supports dependency injection.
- Middleware is applied via a module's configure(consumer: MiddlewareConsumer) method, not via decorators on controllers.
Key NestJS Middleware Takeaways
- Middleware is the right tool when you need to affect the raw request or response early, before NestJS's more specialized request-handling layers engage.
- Choose class-based middleware whenever your logic needs access to other injected providers; otherwise, functional middleware is simpler.
- MiddlewareConsumer's forRoutes() and exclude() methods give you precise, flexible control over exactly where middleware applies.
- Common, battle-tested middleware use cases include logging, request ID tagging, and early-stage security checks like CORS or rate limiting.
NestJS Middleware: Summary
Middleware in NestJS runs at the earliest stage of the request lifecycle, before guards, interceptors, and pipes, giving it direct access to the raw request and response objects along with a next() function to continue the pipeline. NestJS supports both functional middleware, simple functions ideal for stateless logic like logging, and class-based middleware, which implements the NestMiddleware interface and supports full dependency injection for more complex needs. Unlike other NestJS building blocks applied through decorators, middleware is configured inside a module's configure() method using the MiddlewareConsumer API, allowing precise control over which routes or controllers each middleware applies to. Common real-world uses include request logging, request ID tagging for distributed tracing, and early security checks like CORS handling.