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 |
|---|---|---|---|
|
Parses JSON, URL-encoded, raw and text request bodies. |
Active, 2.4.0 |
|
|
Compresses response bodies (gzip, deflate, Brotli) |
Active |
|
|
Parses the |
Active |
|
|
Stores the whole session in a signed cookie on the client |
Active |
|
|
Sets the CORS response headers and answers preflight requests |
Active |
|
|
Development-only error handler that prints the stack trace |
Active |
|
|
Lets clients that can only send GET and POST use PUT, PATCH and DELETE |
Active |
This page |
|
HTTP request logger |
Active |
|
|
Handles |
Active, 2.x |
|
|
Adds an |
Active |
Debugging & Logging and this page |
|
Serves and caches the site icon |
Active |
Serving Static Files and this page |
|
Renders directory listings for a static folder |
Active |
|
|
Serves files from a directory; this is |
Active, 2.x |
|
|
Server-side sessions with a pluggable store |
Active |
|
|
Sets a time limit on a request |
Active |
This page |
|
Dispatches requests to different apps by hostname |
Active |
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
Source: Express: Middleware modules
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 isX-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.
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
PUTandDELETErequests withfetch, 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.
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 |
|---|---|
|
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 |
|
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 |
|
A response deadline for a known slow route, when the handler checks |
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.
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.
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 |
|---|---|---|
|
Deprecated and archived. The npm package, 1.11.0, is marked deprecated |
Set |
|
Deprecated |
|
|
Deprecated |
Express 5 route patterns already use |
|
Removed from the middleware list |
Generate an identifier with |
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 |
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 |
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 |
Removed in Express 4. Each one is a separate package, and body parsing is back in the core as |
|
Bundled version removed long ago; |
|
Removed and deprecated. Use |
|
Still published, but better replaced by Node.js server timeouts and abortable operations |
|
Still works the same, but the body parser must be registered earlier and the field deleted |
Using |
Optional today; |
References
-
Express middleware overview: https://expressjs.com/en/resources/middleware/
-
method-override: https://expressjs.com/en/resources/middleware/method-override/
-
connect-timeout: https://expressjs.com/en/resources/middleware/timeout/
-
serve-favicon: https://expressjs.com/en/resources/middleware/serve-favicon/
-
response-time: https://expressjs.com/en/resources/middleware/response-time/
-
csurf (deprecated): https://expressjs.com/en/resources/middleware/csurf/
-
Using middleware: https://expressjs.com/en/guide/using-middleware/
-
Writing middleware: https://expressjs.com/en/guide/writing-middleware/
-
Express blog: https://expressjs.com/en/blog/
-
Node.js
http.Servertimeouts: https://nodejs.org/api/http.html