NestJS Middleware & Request Pipelines: Intercepting HTTP Execution Streams

Mr. Roy
Published about 3 hours ago
Discover curated collections of blog posts

Mr. Roy
Published about 3 hours ago


Strategic Writer
A technology and business leader with a strong focus on digital transformation, software delivery, and strategic growth. Experienced in leading JavaScript-focused teams, driving business development initiatives, and building innovative SaaS products. Passionate about AI-powered solutions, product development, stakeholder management, and creating scalable digital platforms. Skilled at bridging the gap between business objectives and technology execution while fostering collaboration across clients, teams, and partners.
Get personalized recommendations based on your reading history and interests. Visit the Member Dashboard to see blogs tailored just for you.
In modern high-concurrency platforms, processing an incoming HTTP request involves far more than simply routing data to a target controller method. Before business logic executes, enterprise systems must trace request lifecycles, inspect headers, sanitize payloads, and verify authentication tokens. Failing to intercept these streams cleanly leads to duplicate boilerplate and security blind spots across services.
In this guide, I take a deep dive into NestJS Middleware—the front-line interception layer operating directly ahead of route handlers. I will walk through implementing class-based and functional middleware, binding request context streams, configuring route filters, and handling error propagation for CodeOps AI (my AI Software Engineering Workspace project) using Yarn as the standard package manager.

Understand Middleware Capabilities: Leverage request/response stream access and the next() control function within NestJS pipelines.
Implement Class-Based & Functional Middleware: Design dependency-injected class middleware alongside lightweight functional middleware.
Configure Target Routes & Wildcard Patterns: Chain MiddlewareConsumer methods to target specific controllers, HTTP verbs, and wildcard route splats with Yarn.
Enforce Robust Exception Propagation: Manage asynchronous errors cleanly so NestJS global exception filters capture pipeline failures without hanging connections.
NestJS middleware functions operate before the route handler is selected. They possess full access to the underlying Request and Response objects, as well as the next() callback function that passes control forward down the execution pipeline.
Middleware Type | Dependency Injection Support | CodeOps AI Architectural Use Case |
Class-Based Middleware | Full DI support via constructor injection (@Injectable()). | Asynchronous token verification, tenant resolution, and database logging. |
Functional Middleware | No DI support (Stateless pure function). | Basic request logging, CORS headers, and payload timestamping. |
Global Middleware | Registered via app.use() (Express/Fastify level). | Global security headers (Helmet), body parsing, and compression. |
To build dependency-aware middleware, implement the NestMiddleware interface inside an @Injectable() class. Scaffold the logging middleware using Yarn:
# Scaffold logging middleware using Nest CLI & Yarn
yarn nest g middleware common/middleware/loggerInside LoggerMiddleware, I capture incoming HTTP request metadata and pass control to next():
// common/middleware/logger.middleware.ts
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 userAgent = req.get('user-agent') || '';
console.log(`[CodeOps AI Trace] ${method} ${originalUrl} - ${userAgent}`);
next();
}
}Unlike controllers and providers, middleware cannot be registered directly inside the @Module() decorator array. Instead, module classes implement the NestModule interface and configure middleware via the configure(consumer: MiddlewareConsumer) method:
// app.module.ts
import { Module, NestModule, MiddlewareConsumer, RequestMethod } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { TasksModule } from './tasks/tasks.module';
import { TasksController } from './tasks/tasks.controller';
@Module({
imports: [TasksModule],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.exclude(
{ path: 'tasks/health', method: RequestMethod.GET },
'tasks/public/{*splat}'
)
.forRoutes(TasksController);
}
}When a middleware function requires no external service injection, I recommend using a simple functional middleware to reduce class instantiation overhead:
// common/middleware/request-timestamp.middleware.ts
import { Request, Response, NextFunction } from 'express';
export function requestTimestamp(req: Request, res: Response, next: NextFunction) {
req['requestTime'] = Date.now();
next();
}Registering multiple middleware sequentially in AppModule:
// Chaining multiple middleware functions inside configure()
consumer
.apply(requestTimestamp, LoggerMiddleware)
.forRoutes({ path: 'tasks/{*splat}', method: RequestMethod.ALL });When middleware performs async operations (such as token verification against a remote auth service), throwing built-in NestJS HttpExceptions passes control straight to global exception filters:
// common/middleware/auth.middleware.ts
import { Injectable, NestMiddleware, UnauthorizedException } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class AuthMiddleware implements NestMiddleware {
async use(req: Request, res: Response, next: NextFunction) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
throw new UnauthorizedException('Missing or invalid authorization header');
}
// Simulate async token verification
const token = authHeader.split(' ')[1];
if (token !== 'valid_codeops_key') {
throw new UnauthorizedException('Invalid API key provided');
}
req['user'] = { id: 'usr_101', role: 'engineer' };
next();
}
}Critical Exception Governance Rule
Because middleware executes before route handlers are bound, only global exception filters (
app.useGlobalFilters()) catch exceptions thrown from middleware. Method-scoped or controller-scoped@UseFilters()decorators will NOT catch middleware errors.
To test and validate request pipeline performance under load, I execute these standard Yarn commands:
# Start dev server with hot reload
yarn start:dev
# Validate middleware code structure and typing via ESLint
yarn lint
# Execute integration tests for HTTP request pipelines
yarn test:e2eForgetting to Call next(): Failing to invoke next() or send a response halts request execution indefinitely, resulting in client connection timeouts.
Mutating Global Application State: Avoid storing request-specific state directly on singleton middleware properties. Store request metadata strictly on the req object or rely on AsyncLocalStorage.
Misusing Method-Scoped Exception Filters: Relying on @UseFilters() on controllers to handle middleware failures. Always register global filters to capture pre-routing errors.
Join the Discussion & Share Your Feedback
I’d love to hear your thoughts on this architecture! How are you handling this in your own project? Drop your feedback, questions, or experiences in the comment section below.
NestJS Middleware forms the essential first line of defense in enterprise backend pipelines. By mastering class-based and functional middleware, configuring precise route targets with Yarn, and handling errors asynchronously, I ensure that CodeOps AI processes incoming requests cleanly and securely.
Comments