Express-style functions called before guards, interceptors, pipes, and the route handler. They receive raw req/res objects and either call next() or end the response.

Signature

import { Injectable, NestMiddleware } from "@nestjs/common"
import { NextFunction, Request, Response } from "express"
 
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction): void {
    next()
  }
}

The use() method can be sync, return a Promise, or end the response. Never silently return without calling next() or ending res: the request hangs until the client times out.

Generate with the CLI

nest generate middleware logger      # full form
nest g mi logger                     # short alias → src/logger/logger.middleware.ts
nest g mi logger --flat              # no wrapping folder → src/logger.middleware.ts
nest g mi http/request-id            # nested path → src/http/request-id/request-id.middleware.ts
nest g mi http/request-id --flat     # nested + flat → src/http/request-id.middleware.ts
nest g mi logger --no-spec           # skip the *.spec.ts test file
nest g mi logger --dry-run           # preview the file plan, write nothing

Same shape as the other lifecycle generators (gu, pi, in). The CLI defaults to wrapping the file in a folder named after the element; --flat drops it directly in the target. Source: Nest CLI usages.

Functional middleware

Class middleware can inject providers from the same module. If the middleware needs no dependencies, a plain function is shorter and the official docs prefer it for that case:

import { NextFunction, Request, Response } from "express"
 
export function logger(req: Request, res: Response, next: NextFunction): void {
  next()
}

Functional middleware is bound the same way as class middleware: pass the function reference to consumer.apply(...) or app.use(...).

Why middleware, not a guard or interceptor

Middleware runs first in the request lifecycle and sees only raw HTTP. It has no ExecutionContext: it cannot read decorator metadata, the controller class, or the handler reference. That makes it the right tool for cross-cutting HTTP concerns (helmet, compression, request IDs) and the wrong tool for anything that depends on which handler will run.

NeedUse
Mutate raw req/res for every (matching) routeMiddleware
Decide “should this handler run?” based on roles/permissionsGuard
Validate or coerce a parameter (@Body(), @Param(), @Query())Pipe
Wrap the handler (timing, caching, response mapping, retries)Interceptor
Turn a thrown error into an HTTP responseException filter

Common middleware you’ll plug in

Most apps wire the same handful of Express-ecosystem packages. Nest documents the canonical setup for each:

MiddlewarePackagePurposeBind via
HelmethelmetSecurity headers (CSP, HSTS, X-Frame-Options, …)app.use(helmet()) in main.ts
CORSbuilt-inCross-origin policyapp.enableCors(options) (not app.use)
Compressioncompressiongzip/br response compressionapp.use(compression()) in main.ts
cookie-parsercookie-parserParse Cookie header into req.cookiesapp.use(cookieParser())
express-sessionexpress-sessionServer-side session storeapp.use(session(options))
Body parsersbuilt-inexpress.json() / express.urlencoded() (auto on)Toggle with NestFactory.create(AppModule, { bodyParser: false })

CORS is the odd one: it has a dedicated enableCors() instead of app.use(cors()), because the platform adapter wires it before any user middleware.

Binding

ScopeHowDI access
Globalapp.use(fn) in main.tsNo
Module boundconsumer.apply(LoggerMiddleware).forRoutes(...)Yes
All routesModule-bound class middleware with .forRoutes('*')Yes

There is no middleware slot in @Module() metadata. Module-bound middleware lives in configure() on a class that implements NestModule:

import { MiddlewareConsumer, Module, NestModule, RequestMethod } from "@nestjs/common"
import { CatsController } from "./cats.controller"
import { LoggerMiddleware } from "./logger.middleware"
 
@Module({ controllers: [CatsController] })
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer): void {
    consumer
      .apply(LoggerMiddleware)
      .exclude({ path: "cats/health", method: RequestMethod.GET })
      .forRoutes(CatsController)
  }
}

Middleware has no APP_* token like pipes, guards, interceptors, and filters. The DI-aware path is MiddlewareConsumer; app.use() always bypasses the container.

MiddlewareConsumer

MethodUse
apply(...)Attach one or more class or function middleware. Multiple entries run sequentially.
exclude(...)Skip paths before the final matcher. Accepts strings and { path, method } route objects.
forRoutes(...)Finish the chain by matching strings, route objects, controller classes, or multiple entries.

You can call .apply(A, B, C) to chain several middleware in one go; they run in the order passed.

Order

Middleware runs before every other lifecycle layer. Inside the middleware tier:

  1. Global middleware bound via app.use(...) in main.ts (in bind order).
  2. Module-bound middleware from the root module’s configure() (in apply() order).
  3. Module-bound middleware from imported modules, in imports array order.

After the middleware chain finishes, Nest moves on to guardsinterceptors (pre) → pipes → handler. Source: Request lifecycle.

If a middleware does not end the response, it must call next(). Otherwise the request stays open.

Route matching

PatternMatchesNotes
'cats'exact pathPlain string
'cats/{*splat}'any subpath under catsNamed wildcard segment (path-to-regexp v6 syntax)
{ path, method }path + HTTP methodUse RequestMethod.GET, POST, etc.
CatsControllerevery route declared by the controllerPass the class, not an instance

exclude() must come before forRoutes() because forRoutes() closes the chain. Source: Middleware - Excluding routes.

Common recipes

Body parsers: raw vs json

A body parser is a piece of middleware that reads the request stream once and stores the result on req.body. The choice of parser decides what shape your handler sees. Nest’s Express adapter auto-registers express.json() and express.urlencoded(); the others you bind yourself.

ParserMatches Content-Typereq.body becomesWhen to use
express.json()application/jsonParsed JS object ({ amount: 1 })99% of REST endpoints. Auto-on under Nest.
express.urlencoded()application/x-www-form-urlencodedObject from form fieldsHTML form posts. Auto-on under Nest.
express.raw()Any (configurable, e.g. application/json)Buffer of the original bytesWebhooks where a third party signs the byte-for-byte payload (Stripe, GitHub), or binary uploads
express.text()text/plain (configurable)UTF-8 stringPlain-text payloads, XML you’ll parse yourself

The request body is a one-shot stream: once a parser has consumed it, no other parser can. That’s why mixing them on overlapping paths breaks: whichever runs first wins, and downstream code sees req.body already in that shape (or an empty {} if the type didn’t match).

Why raw matters for signed webhooks: signature verification recomputes an HMAC over the exact bytes the sender hashed. JSON.stringify(req.body) is not guaranteed to reproduce those bytes (key order, whitespace, unicode escapes can all differ), so a re-serialized body will fail the check. express.raw() keeps the original Buffer so you can verify, then JSON.parse(req.body.toString()) yourself.

When to reach for it

  • Mutate the raw request before Nest reaches guards, pipes, or controllers.
  • Attach correlation IDs, request IDs, or low-level logging context.
  • Plug in a third-party Express middleware (helmet, compression, cookie-parser, …).
  • Apply cross-cutting HTTP behavior that is not tied to handler metadata.

When not to

  • Authorization: use a guard. Guards read route metadata and decide whether the handler should run.
  • DTO checks or param coercion: use a pipe. Pipes run with argument metadata.
  • Response mapping, caching, or timing around the handler: use an interceptor.
  • Catching exceptions and shaping the error response: use an exception filter.

Gotchas

Common errors

SymptomLikely cause
Request hangsMiddleware did not call next() and did not end the response
Injected provider is undefinedBound through app.use() instead of MiddlewareConsumer
Custom body parser ignoredForgot { bodyParser: false } on NestFactory.create()
Middleware runs on excluded routeexclude() placed after forRoutes() (the chain closes on forRoutes)
Cannot read handler metadataWrong layer: use a guard or interceptor, middleware has no ExecutionContext
Middleware never fires on a WS gatewayMiddleware is HTTP-only. Move the logic to a guard or interceptor
Stripe webhook signature failsexpress.json() consumed the body before your handler. Use raw() on the webhook path

See also