Functional Middleware

⭐ Interview Importance: HIGH
⏱️ Revision Time: 13 min

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.ts via app.use(logger). You cannot do this with class-based middleware.