Functional Middleware
Functional middleware is a simple function without a class wrapper. It is lightweight, stateless, and identical to standard Express middleware.
Overview
While class-based middleware is powerful because it supports Dependency Injection, sometimes you don’t need all that overhead. If your middleware just needs to check a header, mutate the request object, or act as a proxy, a simple function is sufficient.
NestJS fully supports using standard Express-style functional middleware.
Key Concepts
- Statelessness: Functional middleware cannot inject providers (services, repositories). It has no access to the NestJS IoC container.
- Performance: Because they don’t need to be instantiated or managed by the dependency injection system, functional middleware is slightly faster and consumes less memory.
- Express Ecosystem: You can directly use almost any third-party Express middleware (e.g.,
helmet,cors,morgan) out of the box because they are written as functional middleware.
Code Examples
Writing a Custom Functional Middleware
// logger.middleware.ts
import { Request, Response, NextFunction } from 'express';
// It's just a plain function!
export function logger(req: Request, res: Response, next: NextFunction) {
console.log(`[Functional Middleware] Request to: ${req.url}`);
next(); // Always remember to call next()
}
Applying Functional Middleware (Module Level)
You apply it exactly the same way you apply class-based middleware.
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { logger } from './logger.middleware';
@Module({})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
// Pass the function reference
.apply(logger)
.forRoutes('*'); // Apply to all routes
}
}
Applying Third-Party Functional Middleware
This is the most common use case. For example, using the popular helmet package to secure HTTP headers.
import helmet from 'helmet';
@Module({})
export class SecurityModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
// Call helmet() to get the middleware function, then apply it
.apply(helmet())
.forRoutes('*');
}
}
(Note: For global third-party middleware like helmet, it’s actually more common to apply them globally in main.ts using app.use() rather than inside a module).
Best Practices
- Prefer Functional for Stateless Logic: If your middleware has no dependencies, always choose the functional approach. It’s less boilerplate and technically more performant.
- Global Application: If you are writing a purely functional middleware that needs to run on every request, you can apply it directly in
main.tsviaapp.use(logger). You cannot do this with class-based middleware.