nest_06_middleware - Middleware¶
Middleware runs before your route handler gets the request Logging it , rate limiting it , checking its headers , or just saying "not today" to requests from certain IPs In Express you'd app.use() everything into a monolithic stack - Nest organizes middleware properly
what's in here¶
- Class-based middleware vs functional middleware
- Consumer.apply and route exclusion
- Global middleware registration
- Middleware ordering and execution flow
- Security middleware patterns
class-based middleware¶
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 start = Date.now()
const { method, originalUrl } = req
res.on('finish', () => {
const duration = Date.now() - start
console.log(`${method} ${originalUrl} ${res.statusCode} - ${duration}ms`)
})
next()
}
}
Class-based middleware implements NestMiddleware interface It has access to DI - you can inject services , config , anything The use method receives the Express request , response , and next function Call next() to pass control to the next middleware or route handler
functional middleware¶
For middleware that doesn't need DI , functions are simpler:
import { Request, Response, NextFunction } from 'express'
export function corsMiddleware(req: Request, res: Response, next: NextFunction) {
res.header('Access-Control-Allow-Origin', req.headers.origin || '*')
res.header('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,PATCH')
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
if (req.method === 'OPTIONS') {
return res.sendStatus(204)
}
next()
}
No @Injectable() , no class boilerplate Use functional middleware for stateless operations (logging , header manipulation , simple checks) Use class middleware when you need injected dependencies
applying middleware with Consumer¶
Middleware is configured in a module's configure method:
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'
import { LoggerMiddleware } from './common/middleware/logger.middleware'
import { AuthMiddleware } from './auth/auth.middleware'
@Module({
imports: [],
controllers: [UsersController, AuthController],
providers: []
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware) // apply this middleware
.forRoutes('*') // to all routes
consumer
.apply(AuthMiddleware)
.exclude( // but exclude these paths
{ path: 'auth/login', method: RequestMethod.POST },
{ path: 'auth/register', method: RequestMethod.POST },
{ path: 'health', method: RequestMethod.GET }
)
.forRoutes('*')
}
}
consumer.apply() accepts one or more middleware .forRoutes() accepts path patterns , controller classes , or route info objects .exclude() accepts paths to skip - auth middleware shouldn't block the login endpoint
route-specific middleware¶
configure(consumer: MiddlewareConsumer) {
consumer
.apply(AdminOnlyMiddleware)
.forRoutes(
{ path: 'admin/*', method: RequestMethod.ALL },
{ path: 'users/ban', method: RequestMethod.POST }
)
consumer
.apply(RateLimitMiddleware)
.forRoutes({ path: 'auth/login', method: RequestMethod.POST })
}
Apply middleware only where it's needed Admin middleware on admin routes , rate limiting on auth endpoints Don't apply security middleware globally when it's only relevant to specific routes
global middleware¶
Register middleware that runs on every request:
// main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule)
// global middleware
app.use(helmet())
app.use(corsMiddleware)
// or use the consumer approach with APP_* tokens
// (covered in the guards section)
await app.listen(3000)
}
app.use() registers middleware on the underlying Express instance Use it for truly global concerns - security headers , CORS , request ID generation
middleware ordering¶
Middleware executes in the order it's registered
configure(consumer: MiddlewareConsumer) {
// 1. Security headers run first
consumer.apply(SecurityHeadersMiddleware).forRoutes('*')
// 2. Then request logging
consumer.apply(RequestLoggerMiddleware).forRoutes('*')
// 3. Then auth check
consumer.apply(AuthMiddleware).exclude(...).forRoutes('*')
// 4. Then rate limiting
consumer.apply(RateLimitMiddleware).forRoutes(...)
}
Security headers first - before any response is sent Logging second - capture the raw request Auth third - reject unauthorized before they touch anything Rate limiting last - only on specific routes that need it
Order your middleware with security in mind , not convenience
security middleware patterns¶
// IP allowlist middleware
@Injectable()
export class IpWhitelistMiddleware implements NestMiddleware {
constructor(private configService: ConfigService) {}
use(req: Request, res: Response, next: NextFunction) {
const allowedIps = this.configService.get<string>('ADMIN_ALLOWED_IPS')?.split(',') || []
const clientIp = req.ip || req.socket.remoteAddress
if (!allowedIps.includes(clientIp) && allowedIps.length > 0) {
return res.status(403).json({ message: 'access denied from this IP' })
}
next()
}
}
// Request sanitization middleware
@Injectable()
export class SanitizationMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
if (req.body && typeof req.body === 'object') {
// strip __proto__ and constructor to prevent prototype pollution
const sanitize = (obj: any) => {
for (const key of Object.keys(obj)) {
if (key === '__proto__' || key === 'constructor') {
delete obj[key]
} else if (typeof obj[key] === 'object') {
sanitize(obj[key])
}
}
}
sanitize(req.body)
}
next()
}
}
IP whitelisting for admin endpoints and prototype pollution sanitization in middleware prevents attacks before they reach your controllers
prerequisites¶
nest_05_modules - Modules & Module Patterns