Official Middleware Modules

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Express 5.3 (npm express 5.3.0, released 2026-10-09; Express 4 is at 4.22.3) and Node.js 18 or later, and was checked on 2026-10-29. Express itself ships only a small set of built-in middleware functions (express.json(), express.urlencoded(), express.raw(), express.text(), express.static() and express.Router()). Everything else comes from modules, and the Express team maintains a short list of them under the expressjs organization on GitHub. They are listed on the official middleware page of the Express website and are the safest default when a feature is missing, because they follow the framework’s release cadence, security process and supported Node.js versions. For the middleware contract itself ((req, res, next), ordering, error-handling middleware), see Middleware Fundamentals.

The sixteen team-maintained modules

Each module lives in its own npm package and its own repository, with its own version number: the version of a middleware package is unrelated to the Express version. The table lists what each module is for, its status on 2026-10-29 and the sibling page that documents it in depth. Modules without a sibling page are documented later on this page.

Module Purpose Status Documented in

body-parser

Parses JSON, URL-encoded, raw and text request bodies. express.json() and friends are this package, re-exported

Active, 2.4.0

Body Parsing

compression

Compresses response bodies (gzip, deflate, Brotli)

Active

Performance Best Practices

cookie-parser

Parses the Cookie header into req.cookies and req.signedCookies

Active

Cookies & Sessions

cookie-session

Stores the whole session in a signed cookie on the client

Active

Cookies & Sessions

cors

Sets the CORS response headers and answers preflight requests

Active

CORS

errorhandler

Development-only error handler that prints the stack trace

Active

Error Handling

method-override

Lets clients that can only send GET and POST use PUT, PATCH and DELETE

Active

This page

morgan

HTTP request logger

Active

Debugging & Logging

multer

Handles multipart/form-data uploads

Active, 2.x

File Uploads & Downloads

response-time

Adds an X-Response-Time header

Active

Debugging & Logging and this page

serve-favicon

Serves and caches the site icon

Active

Serving Static Files and this page

serve-index

Renders directory listings for a static folder

Active

Serving Static Files

serve-static

Serves files from a directory; this is express.static()

Active, 2.x

Serving Static Files

express-session

Server-side sessions with a pluggable store

Active

Cookies & Sessions

connect-timeout

Sets a time limit on a request

Active

This page

vhost

Dispatches requests to different apps by hostname

Active

Routers & Modular Apps

Two naming details trip people up. The Express website calls two of the pages session and timeout, but the npm packages are express-session and connect-timeout. And body-parser and serve-static are listed as separate modules even though most applications never install them directly: the framework re-exports their main functions as express.static() (since Express 4.0), express.json() and express.urlencoded() (since 4.16), and express.raw() and express.text() (since 4.17), and Express 5 depends on body-parser 2.x and serve-static 2.x. Installing them separately is only needed to pin a different version.

The Express website also links a few community projects from its middleware overview (for example helmet and passport). They are not maintained by the Express team, so treat them as third-party modules, as described at the end of this page.

Installing and registering middleware modules

All of them are installed from npm and registered with app.use() or on a single route, in the order the request should pass through them. The usual production order is: logging and timing, security headers and CORS, compression, body parsing, cookies and sessions, application routes, a 404 handler and last the error handler.

npm install method-override connect-timeout serve-favicon response-time morgan

All of the examples below use ES modules and the node: prefix for core modules, so they need "type": "module" in package.json. The running scenario is a small bookshelf API with a catalog of books.

method-override

HTML forms can only submit GET and POST. A server-rendered page that edits or deletes a book therefore has no way to send a real PUT or DELETE request, and some corporate proxies and old HTTP clients also block those verbs. method-override bridges the gap: the client sends a POST and says in a header, a query parameter or a body field which method it really means, and the middleware rewrites req.method before routing happens. The rewrite only works if the middleware is registered before the routes it should affect.

methodOverride(getter, options) takes a getter that decides where the override is read from:

  • a string that starts with X- is read from that request header (the conventional name is X-HTTP-Method-Override);

  • any other string is read as a query-string key, for example _method;

  • a function (req, res) returns the method name, or nothing to leave the request alone. This is how a hidden form field is read.

The options.methods option lists the original methods that may be overridden. It defaults to POST only, so a GET request carrying an override header is ignored, which is deliberate: allowing GET to turn into DELETE would make an image tag or a link able to delete data. Keep the default unless there is a very specific reason to change it.

Overriding the method from a header, a query key and a hidden form field (method-override.js)
import { createServer } from 'node:http'
import express from 'express'
import methodOverride from 'method-override'

const app = express()
const books = new Map([[1, { id: 1, title: 'Dune', read: false }]])

// 1. Header: POST /books/1 with X-HTTP-Method-Override: DELETE
app.use(methodOverride('X-HTTP-Method-Override'))

// 2. Query string: POST /books/1?_method=PUT
app.use(methodOverride('_method'))

// 3. Hidden form field named _method. The body has to be parsed first,
//    and the field is removed so that it does not reach the handler.
app.use(express.urlencoded({ extended: false }))
app.use(
  methodOverride((req) => {
    if (req.body && typeof req.body === 'object' && '_method' in req.body) {
      const method = req.body._method
      delete req.body._method
      return method
    }
  })
)

app.put('/books/:id', (req, res) => {
  const book = books.get(Number(req.params.id))
  if (!book) return res.sendStatus(404)
  book.read = req.body.read === 'on'
  res.json(book)
})

app.delete('/books/:id', (req, res) => {
  res.sendStatus(books.delete(Number(req.params.id)) ? 204 : 404)
})

createServer(app).listen(3000, () => {
  console.log('Bookshelf listening on http://localhost:3000')
})

The matching form in a view looks like the following; the browser sends a POST and the middleware turns it into a PUT before the router sees it.

<form method="post" action="/books/1">
  <input type="hidden" name="_method" value="PUT">
  <label><input type="checkbox" name="read"> Read</label>
  <button type="submit">Save</button>
</form>

Points to keep in mind:

  • Method override changes semantics, so a form that triggers a state change is exposed to cross-site request forgery exactly as any other POST is. Protect it with SameSite cookies and a CSRF token (see Security Best Practices).

  • Reading the override from the body requires a body parser registered earlier, and the field should be deleted so validation schemas do not see an unknown key.

  • A single-page application or an API client can send real PUT and DELETE requests with fetch, so there is nothing to override for them. The module is mostly useful for server-rendered pages with plain forms.

  • The module is at version 3.0.0 and has no Express 5 specific behavior to worry about.

connect-timeout

connect-timeout starts a timer when the request enters the middleware. When the time limit passes before the response is finished, it sets req.timedout to true and, by default, forwards a 503 error to the error handler through next(err). It takes a time as a number of milliseconds or as a string accepted by the ms module ('5s', '2m') and an options object whose respond option (default true) controls the automatic error. With respond: false the module only emits a timeout event on the request and sets the flag, and the application decides what to do.

The crucial detail is what the module does not do: it does not cancel anything. A slow database query or an outbound HTTP call keeps running after the timer fires, and a handler that later calls res.json() will either send a second response or throw ERR_HTTP_HEADERS_SENT. Every step that follows a slow asynchronous call must therefore check req.timedout first. This is also why registering it globally with app.use() is discouraged by the module’s own documentation: the flag then has to be checked in every middleware of every route, and a forgotten check is a bug that only appears under load. Apply it to the specific routes known to be slow, and keep a general limit at the Node.js level.

A time limit on one slow report route, with the flag checked after the slow step (timeout.js)
import { setTimeout as sleep } from 'node:timers/promises'
import { createServer } from 'node:http'
import express from 'express'
import timeout from 'connect-timeout'

const app = express()

function haltOnTimedout(req, res, next) {
  if (!req.timedout) next()
}

async function buildReport(req, res, next) {
  try {
    await sleep(8_000) // stands in for a slow aggregation over the catalog
    if (req.timedout) return // the client already received the 503
    req.report = { totalBooks: 3 }
    next()
  } catch (err) {
    next(err)
  }
}

app.get(
  '/reports/catalog',
  timeout('5s'),
  buildReport,
  haltOnTimedout,
  (req, res) => res.json(req.report)
)

// The default response is a 503 error passed to the error handler.
app.use((err, req, res, next) => {
  if (res.headersSent) return next(err)
  res.status(err.status ?? 500).json({ error: err.code ?? 'internal_error' })
})

const server = createServer(app)
server.listen(3000)

Better ways to bound time exist and are usually combined with, or preferred to, this module:

Mechanism What it protects

server.requestTimeout, server.headersTimeout and server.keepAliveTimeout on the Node.js http.Server

Slow or stalled clients: how long Node waits for the full request, for the headers and for the next request on an idle connection. This is the correct place for a server-wide limit

AbortSignal.timeout(ms) passed to fetch, database drivers and other APIs that accept a signal

The slow work itself: the operation is actually cancelled instead of being left running

A reverse proxy or load balancer limit

The whole path from the client, including time spent outside Node.js

connect-timeout on selected routes

A response deadline for a known slow route, when the handler checks req.timedout

serve-favicon

Browsers request /favicon.ico on almost every page load. serve-favicon answers that request from memory, with a long-lived cache header, before any logger or session middleware runs, so the icon never pollutes the logs or creates sessions. Register it early and give it the path to the icon; the file is read once at startup and the middleware throws at startup if the path is wrong. The maxAge option sets the cache lifetime in milliseconds or as an ms string and defaults to one year.

Serving the favicon before anything else (favicon.js)
import { createServer } from 'node:http'
import { fileURLToPath } from 'node:url'
import express from 'express'
import favicon from 'serve-favicon'

const app = express()
const iconPath = fileURLToPath(new URL('./public/favicon.ico', import.meta.url))

app.use(favicon(iconPath, { maxAge: '7d' }))
app.use(express.static(fileURLToPath(new URL('./public', import.meta.url))))

createServer(app).listen(3000)

Many modern sites declare icons with <link rel="icon"> tags and serve them from express.static(), in which case this module is optional. It remains handy for an API server that still receives stray /favicon.ico requests from browsers.

response-time

response-time records how long a request took from the moment the middleware ran until the response headers were written, and sends it in an X-Response-Time header. Register it first so that it measures the whole stack that follows. The options digits (decimal places, default 3), header (the header name) and suffix (adds the ms unit, default true) tune the header. Passing a function (req, res, time) instead of options lets the application record the value in a metrics system and skip the header.

Adding the header and reporting slow requests to a metrics hook (response-time.js)
import { createServer } from 'node:http'
import express from 'express'
import responseTime from 'response-time'

const app = express()

app.use(responseTime({ digits: 1 }))

app.use(
  responseTime((req, res, time) => {
    if (time > 500) {
      console.warn(`slow request ${req.method} ${req.originalUrl}: ${time.toFixed(0)} ms`)
    }
  })
)

app.get('/books', (req, res) => res.json([{ id: 1, title: 'Dune' }]))

createServer(app).listen(3000)

Exposing timings to every client can leak information about back-end work, so many deployments only keep them in internal environments or in the logs. The morgan logger offers a :response-time token for the same purpose, covered in Debugging & Logging.

Deprecated and removed modules

In May 2025 the Express team announced that three modules of the list are deprecated and no longer receive fixes. A fourth, connect-rid, was removed from the list. The npm packages still install and still work on current Node.js releases, but a module without a maintainer is a liability for something as sensitive as request handling.

Module Status Replacement

csurf

Deprecated and archived. The npm package, 1.11.0, is marked deprecated

Set SameSite=Lax or Strict on session cookies, check the Origin and Sec-Fetch-Site headers, and use a maintained token library such as csrf-csrf (double-submit cookie) or csrf-sync (synchronizer token). See Security Best Practices

connect-multiparty

Deprecated

multer 2.x for multipart/form-data into memory or disk, or busboy or formidable for streaming control. See File Uploads & Downloads

path-match

Deprecated

Express 5 route patterns already use path-to-regexp 8 through the router. For matching a path yourself, use path-to-regexp directly with match()

connect-rid

Removed from the middleware list

Generate an identifier with crypto.randomUUID() in a small middleware and place it on req.id and in res.locals

The request-identifier replacement is only a few lines.

import { randomUUID } from 'node:crypto'

export function requestId(req, res, next) {
  req.id = req.get('x-request-id') ?? randomUUID()
  res.set('X-Request-Id', req.id)
  next()
}

The most common surprise for developers following older tutorials is csurf: many pre-2020 examples add it to every form application. There is no drop-in replacement, because the replacement libraries differ in how tokens are stored, so migrate form by form and test both the token delivery and the rejection path. The migration list for the whole framework is in Migrating & What’s Changed.

Judging third-party middleware

The npm registry has thousands of Express middleware modules, and a package that is popular is not necessarily safe or maintained. Before adding one, check the following:

Question What to look for

Is it maintained?

A release or commit within the last year, open issues that get answers, and no deprecation notice on npm or in the README

Does it support Express 5?

The peerDependencies or README mention Express 5, or the tests run against it. Middleware written for Express 4 that uses removed APIs (req.param(), res.send(status, body), app.del) breaks. Wildcards in route strings must follow the path-to-regexp 8 syntax

Does it handle async errors?

In Express 5 a rejected promise from a handler goes to the error handler. Older modules that call a callback with a thrown error still work, but a module that swallows promise rejections hides bugs

What is its security history?

Published advisories on GitHub or in the npm audit output, how fast they were fixed, and whether the package has an unusually large dependency tree

Does it do something that the platform already does?

Many tiny modules are redundant with built-ins such as express.json(), node:crypto, AbortSignal.timeout() or fetch

Who publishes it?

An organization with several maintainers is more durable than a single account; check the repository and the npm provenance information when available

Install with exact versions in production lockfiles, run npm audit in continuous integration, and keep the number of middleware modules that touch every request small, because each one adds latency and an attack surface.

Where the books are outdated

Introductory books on Express commonly include chapters that rely on middleware which is gone or changed. The table summarizes the differences without repeating the books' material.

Concept in older material Current state

Middleware bundled with Connect and exposed as express.bodyParser(), express.cookieParser(), express.session(), express.logger(), express.methodOverride()

Removed in Express 4. Each one is a separate package, and body parsing is back in the core as express.json() and express.urlencoded()

express.csrf() or csurf on every form

Bundled version removed long ago; csurf is now deprecated. Use SameSite cookies and a maintained token library

express.multipart() or connect-multiparty for uploads

Removed and deprecated. Use multer 2.x

connect-timeout applied globally with app.use()

Still published, but better replaced by Node.js server timeouts and abortable operations

method-override reading req.body._method without parsing the body first

Still works the same, but the body parser must be registered earlier and the field deleted

Using serve-favicon as the only way to avoid favicon noise

Optional today; express.static() and a link tag cover most sites