Skip to content

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


next -> nest_07_guards - Guards & Authorization