# expressjs/express — codebase unpack

> Canonical page: https://codevetter.com/unpack/express

This is a deterministic Repo Unpack scan of expressjs/express — a structural map, not a review, security audit, or maintainer-endorsed description.

- Scanned commit: 9a34acf03cb818ff3f8bc40e44176e277a25cbb9 (2026-09-15)
- Last upstream commit: fix(res.send): preserve ETag generation with Transfer-Encoding (#7459)
- Generated by codevetter 1.12.0 on 2026-09-22
- 214 files scanned · 700 KB

## Languages

- JavaScript: 141 files
- HTML: 8 files
- YAML: 7 files
- CSS: 4 files
- Markdown: 4 files
- JSON: 1 files

## Repository structure

- test/ — 112 files
- examples/ — 80 files
- lib/ — 6 files
- .github/ — 6 files

Probable source entrypoint: `index.js` (inferred from the tree, not upstream docs)

## Source layout — lib/

- lib/application.js — 1 file
- lib/express.js — 1 file
- lib/request.js — 1 file
- lib/response.js — 1 file
- lib/utils.js — 1 file
- lib/view.js — 1 file

## Package

- package.json: express v5.2.1 — 44 declared dependencies

## Structural health signals

Average score 7.9/10 across 96 files; 16 hotspots. These are deterministic review leads, not proof of a bug.

## Recent history

- 9a34acf03cb8 (2026-09-16) fix(res.send): preserve ETag generation with Transfer-Encoding (#7459)
- 3ce6d0eb86e9 (2026-09-13) ci: add npm staged publication with dist-tag support (#7464)
- 53d4a0d606c0 (2026-09-11) docs: fix capitalization of GitHub Discussions in Readme (#7426)
- bed501c695a6 (2026-09-08) build(deps-dev): bump morgan from 1.11.0 to 1.12.0 (#7461)
- 4e65ec983d33 (2026-09-08) build(deps): bump the github-actions group with 5 updates (#7462)

## Overview

Express 5.2.1 is the minimalist Node.js HTTP framework, and this repository holds only its core: ~5,500 lines across six files in `lib/` that layer settings management, request/response sugar, and a template-resolution system on top of Node's `http` primitives. What is distinctive about the v5 line is how much has been pushed out of the tree — routing lives entirely in the external `router` package, body parsing in `body-parser`, static serving in `serve-static` — so this repo is mostly a prototype-mixin façade plus a large behavioural test suite (70 unit files, 18 acceptance files that boot the `examples/` apps). It targets Node >= 18 and ships to npm as a library; there is no build step, no bundler, and no storage layer. The audience is every Node web developer, and the maintenance posture is visibly conservative: pinned SHA GitHub Actions, OpenSSF Scorecard, CodeQL, dependabot with major-version updates ignored.

## System map

A single npm entrypoint (`index.js` → `lib/express.js`) exposes a factory, `createApplication()`, that returns a callable function decorated with the application prototype and EventEmitter. Five runtime modules split responsibility: `application.js` (settings, dispatch, mounting, rendering), `request.js` and `response.js` (prototypes grafted onto `http.IncomingMessage`/`http.ServerResponse`), `view.js` (template lookup), and `utils.js` (compilers for etag/query/trust). The runtime boundary is deliberately thin — routing, body parsing and static file serving are re-exported from sibling npm packages rather than implemented here. There is no persistent storage; all state is per-app in-memory objects. Distribution is npm-only, driven by a GitHub release event.
- `index.js` is a three-line re-export of `lib/express.js`, and `package.json` restricts the published tarball to `LICENSE`, `Readme.md`, `index.js` and `lib/` — so `test/` and `examples/` never ship. — [index.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/index.js) [package.json#L95-L100](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/package.json#L95-L100)
- `createApplication()` builds `app` as a closure that forwards to `app.handle(req, res, next)`, then uses `merge-descriptors` to mixin `EventEmitter.prototype` and the `application` proto (both with `redefine=false`), and finally calls `app.init()`. — [lib/express.js#L36-L56](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/express.js#L36-L56)
- Per-app request/response prototypes are created with `Object.create(req, { app: {...value: app} })`, which is why every `req`/`res` getter can reach `this.app.get('trust proxy fn')` without a closure. — [lib/express.js#L44-L51](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/express.js#L44-L51) [lib/request.js#L297-L343](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/request.js#L297-L343)
- Routing is not implemented in this repo: `lib/express.js` requires the external `router` package and re-exports `Router` and `Router.Route`; `app.router` is a lazy getter that instantiates `new Router({ caseSensitive, strict })` on first access. — [lib/express.js#L19-L71](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/express.js#L19-L71) [lib/application.js#L59-L84](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L59-L84) [package.json#L52](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/package.json#L52)
- Body parsing is a pure re-export — `express.json`, `express.raw`, `express.text`, `express.urlencoded` all point straight at `body-parser`, and `express.static` at `serve-static`. — [lib/express.js#L77-L81](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/express.js#L77-L81) [test/exports.js#L11-L35](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/exports.js#L11-L35)
- All per-app state lives in three null-prototype objects created in `app.init()` — `this.cache` (compiled views), `this.engines` (extension→renderer), `this.settings` — plus `this.locals`; there is no database or persistence layer anywhere in `lib/`. — [lib/application.js#L59-L66](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L59-L66) [lib/application.js#L118-L128](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L118-L128)
- `app.handle()` is the single dispatch boundary: it builds a `finalhandler` terminator, sets `X-Powered-By`, wires the `req.res`/`res.req` circular refs, swaps both prototypes with `Object.setPrototypeOf`, and delegates to `this.router.handle`. — [lib/application.js#L152-L182](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L152-L182)
- The external integration surface is 28 runtime dependencies, of which the load-bearing ones are `proxy-addr` (trust proxy), `qs`/`node:querystring` (query), `send`+`serve-static` (file IO), `etag`+`fresh` (conditional GET), `cookie`+`cookie-signature`, and `finalhandler`. — [package.json#L27-L56](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/package.json#L27-L56) [lib/utils.js#L16-L23](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/utils.js#L16-L23) [lib/response.js#L15-L36](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L15-L36)
- There is no build step. The npm scripts are `lint` (eslint), `test` (mocha over `test/` and `test/acceptance/` with `--check-leaks`), `test-ci` (nyc → lcovonly), `test-cov` (nyc → html) and `test-tap`. — [package.json#L101-L107](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/package.json#L101-L107)
- CI runs a 2-OS × 9-Node matrix (ubuntu/windows × Node 18–26) in `ci.yml`, with a separate `legacy.yml` still exercising Node 16 and 17 despite `engines.node` declaring `>= 18`. — [.github/workflows/ci.yml#L44-L52](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/.github/workflows/ci.yml#L44-L52) [.github/workflows/legacy.yml#L30-L36](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/.github/workflows/legacy.yml#L30-L36) [package.json#L91-L93](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/package.json#L91-L93)
- Deployment is npm publish triggered by GitHub release `published`; the workflow derives the dist-tag from `package.json` version rather than the release branch (`next` for prereleases, `latest` for the newest major, `latest-<major>` for maintenance lines). — [.github/workflows/npm-publish.yml#L37-L79](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/.github/workflows/npm-publish.yml#L37-L79)
- Supply-chain hardening is explicit: `.npmrc` disables lockfiles, enforces a 7-day minimum release age, sets `ignore-scripts=true` and `allow-git=none`; every Action in CI is pinned to a full commit SHA. — [.npmrc](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/.npmrc) [.github/workflows/ci.yml#L28-L35](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/.github/workflows/ci.yml#L28-L35) [.github/workflows/scorecard.yml#L34-L40](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/.github/workflows/scorecard.yml#L34-L40)
- `app.listen()` is a thin wrapper over `http.createServer(this)` that only adds `once`-wrapped error forwarding when the last argument is a callback — HTTPS is left to the caller. — [lib/application.js#L598-L607](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L598-L607) [test/app.listen.js#L7-L26](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/app.listen.js#L7-L26)
- Observability is limited to two `debug` namespaces, `express:application` and `express:view`; runtime errors reaching `finalhandler` are console-logged via `logerror` unless `env === 'test'`. — [lib/application.js#L17](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L17) [lib/application.js#L615-L618](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L615-L618) [lib/view.js#L18](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/view.js#L18)

## Feature catalog

The user-facing surface is three prototypes plus a factory. `app` provides configuration (`set`/`get`/`enable`/`disable`/`enabled`/`disabled`), composition (`use`, `route`, `param`, `all`, one method per `http.METHODS` entry), templating (`engine`, `render`) and lifecycle (`listen`, `path`). `res` provides ~24 response helpers built around `res.send`. `req` provides content negotiation and a set of lazily-computed getters derived from headers and the trust-proxy setting. The `examples/` tree doubles as documentation for 26 usage patterns and as the fixture set for acceptance tests.
- HTTP verb methods are generated dynamically: `methods.forEach` iterates `require('node:http').METHODS` lowercased and defines `app[method]`, with `app.get(name)` special-cased to read a setting when called with exactly one argument. — [lib/application.js#L471-L483](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L471-L483) [lib/utils.js#L29](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/utils.js#L29)
- `app.all(path, ...fns)` registers the same handlers against every method in the generated `methods` list rather than using a wildcard. — [lib/application.js#L492-L501](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L492-L501) [test/app.all.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/app.all.js)
- `app.set()` has four magic settings that compile a companion function: `etag`→`etag fn`, `query parser`→`query parser fn`, `trust proxy`→`trust proxy fn`; each compiler throws `TypeError` on unknown string values. — [lib/application.js#L354-L382](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L354-L382) [lib/utils.js#L130-L215](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/utils.js#L130-L215) [test/config.js#L41-L52](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/config.js#L41-L52)
- Defaults set at boot: `x-powered-by` enabled, `etag` = `weak`, `query parser` = `simple` (node querystring), `subdomain offset` = 2, `trust proxy` = false, `jsonp callback name` = `callback`, `views` = `resolve('views')`, and `view cache` auto-enabled when `NODE_ENV === 'production'`. — [lib/application.js#L90-L145](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L90-L145)
- `app.use()` detects a mounted sub-app by duck-typing `fn.handle && fn.set`, wraps it in a `mounted_app` middleware that restores the parent's `req`/`res` prototypes on the way out, sets `mountpath`/`parent`, and emits `'mount'`. — [lib/application.js#L190-L241](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L190-L241) [test/app.use.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/app.use.js)
- The `'mount'` listener installed in `defaultConfiguration` makes a child app prototype-inherit the parent's `request`, `response`, `engines` and `settings`, and drops its own default `trust proxy` so the parent's wins. — [lib/application.js#L109-L123](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L109-L123)
- `res.send` is the funnel for almost every response: it type-switches on the body, sets Content-Type, computes Content-Length (skipped when `Transfer-Encoding` is present), generates an ETag, downgrades to 304 when `req.fresh`, strips headers for 204/304/205, and skips the body for HEAD. — [lib/response.js#L126-L226](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L126-L226)
- `res.json` and `res.jsonp` share a `stringify()` helper honouring the `json replacer`, `json spaces` and `json escape` settings, where `json escape` rewrites `<`, `>`, `&` to `\u003c`/`\u003e`/`\u0026` to defeat HTML sniffing. — [lib/response.js#L236-L260](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L236-L260) [lib/response.js#L1025-L1049](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L1025-L1049)
- `res.sendFile(path, opts, cb)` requires an absolute path or an explicit `opts.root`, `encodeURI`s the path, wires `opts.etag` from the app setting, and pipes a `send` stream through the local `sendfile()` state machine; `EISDIR` falls through to `next()`. — [lib/response.js#L375-L430](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L375-L430) [lib/response.js#L923-L1010](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L923-L1010)
- `res.download()` layers `Content-Disposition` (via `content-disposition`) on top of `res.sendFile`, explicitly dropping any user-supplied `content-disposition` header and resolving the path when no `root` is given. — [lib/response.js#L437-L486](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L437-L486) [test/res.download.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/res.download.js)
- `res.format(obj)` negotiates on the object's keys via `req.accepts`, always sets `Vary: Accept`, falls back to an optional `default` key, and otherwise calls `next(createError(406, { types }))`. — [lib/response.js#L573-L604](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L573-L604) [test/res.format.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/res.format.js)
- `res.redirect` is itself implemented on top of `res.format`, emitting a plain-text or escaped-HTML body for the target, and uses `depd` deprecation warnings for missing/non-string URLs and non-numeric statuses. — [lib/response.js#L814-L870](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L814-L870) [lib/response.js#L17](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L17)
- Cookie support: `res.cookie` JSON-prefixes object values with `j:`, signs with `s:` + `cookie-signature` when `opts.signed` (requiring `req.secret` from cookie-parser), converts `maxAge` to both `expires` and seconds, and defaults `path` to `/`; `res.clearCookie` forces `expires: new Date(1)` and deletes `maxAge`. — [lib/response.js#L714-L777](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L714-L777) [test/res.cookie.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/res.cookie.js) [test/res.clearCookie.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/res.clearCookie.js)
- Request-side features are all lazy getters defined through a local `defineGetter` helper: `query`, `protocol`, `secure`, `ip`, `ips`, `subdomains`, `path`, `host`, `hostname`, `fresh`, `stale`, `xhr` — plus the methods `get`/`header`, `accepts*`, `range`, `is`. — [lib/request.js#L230-L527](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/request.js#L230-L527)
- `req.fresh` only performs weak validation for `GET`, `HEAD` and `QUERY` methods and only for 2xx/304 statuses, reading `ETag` and `Last-Modified` back off the response. — [lib/request.js#L469-L486](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/request.js#L469-L486) [test/req.fresh.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/req.fresh.js)
- The `examples/` tree is a catalogued feature gallery of 26 runnable apps (auth, mvc, vhost, multi-router, view-constructor, web-service, …), indexed in `examples/README.md`. — [examples/README.md#L1-L28](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/examples/README.md#L1-L28) [examples/mvc/index.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/examples/mvc/index.js) [examples/view-constructor/index.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/examples/view-constructor/index.js)

## Data flow

Data enters as a raw Node `IncomingMessage`, is re-prototyped in `app.handle` so that Express getters become available, then flows through the external router's middleware stack where `body-parser` populates `req.body` and the router populates `req.params`, `req.baseUrl` and `req.next`. Derived request values are computed lazily and are never cached — every read of `req.query`, `req.ip` or `req.host` re-runs its parser against the raw headers, gated by the compiled `trust proxy fn`. Output converges on `res.send`, which is where Content-Type, Content-Length, ETag and freshness negotiation are decided before `res.end`. Template rendering is a side channel: `res.render` → `app.render` → `View#render` produces a string that is then fed back into `res.send`. Anything that escapes the stack terminates in `finalhandler`.
- The input boundary is `app.handle(req, res, callback)`, which mutates the incoming Node objects in place via `Object.setPrototypeOf(req, this.request)` / `(res, this.response)` — Express never wraps or copies the request. — [lib/application.js#L152-L182](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L152-L182)
- `res.locals` is lazily initialised to `Object.create(null)` on each dispatch only if absent, so a sub-app mount reuses the parent's locals object rather than resetting it. — [lib/application.js#L175-L178](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L175-L178)
- `req.query` is recomputed on every property read: it fetches `query parser fn` from app settings, extracts the raw string with `parseurl`, and returns `Object.create(null)` when parsing is disabled (`query parser` set to `false`). — [lib/request.js#L230-L241](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/request.js#L230-L241) [lib/utils.js#L162-L192](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/utils.js#L162-L192)
- The `extended` query parser routes through `qs.parse(str, { allowPrototypes: true })`, while the default `simple` parser is `node:querystring.parse` — two materially different object shapes behind one setting. — [lib/utils.js#L162-L192](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/utils.js#L162-L192) [lib/utils.js#L267-L271](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/utils.js#L267-L271)
- All proxy-derived values funnel through one compiled predicate: `compileTrust` turns `true`, a hop count, a comma-separated string, an array or a function into `trust(addr, index)`, consumed identically by `req.protocol`, `req.ip`, `req.ips` and `req.host`. — [lib/utils.js#L194-L215](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/utils.js#L194-L215) [lib/request.js#L297-L431](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/request.js#L297-L431)
- `req.protocol` and `req.host` both defensively split their `X-Forwarded-*` header on the first comma, taking only the left-most value. — [lib/request.js#L307-L314](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/request.js#L307-L314) [lib/request.js#L418-L431](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/request.js#L418-L431)
- `req.ips` reverses `proxyaddr.all()` and pops the socket address, so the returned array is farthest-to-closest and excludes the direct peer. — [lib/request.js#L357-L366](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/request.js#L357-L366) [test/req.ips.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/req.ips.js)
- On the way out, `res.set('Content-Type', ...)` always runs the value through `mime.contentType()` to append a charset, and throws `TypeError` if an array is supplied for Content-Type. — [lib/response.js#L669-L691](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L669-L691) [test/res.set.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/res.set.js)
- ETag generation is a two-step: `compileETag` picks `wetag`/`etag` from `lib/utils.js`, `res.send` calls it only when no ETag header is already present and a length was computed, and the buffer conversion is skipped for chunks under 1000 bytes when no ETag is needed. — [lib/response.js#L161-L193](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L161-L193) [lib/utils.js#L40-L51](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/utils.js#L40-L51) [lib/utils.js#L249-L258](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/utils.js#L249-L258)
- Render data is merged in a fixed precedence — `app.locals`, then `res.locals` (passed as `opts._locals`), then the caller's options — with `cache` defaulting to the `view cache` setting when not explicitly provided. — [lib/application.js#L522-L546](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L522-L546) [lib/response.js#L896-L920](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L896-L920)
- Compiled views are memoised by name in `app.cache` only when `renderOptions.cache` is truthy, which is why development mode re-stats the filesystem on every render. — [lib/application.js#L536-L575](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L536-L575) [lib/view.js#L104-L128](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/view.js#L104-L128)
- `View#render` normalises synchronous engine callbacks by copying `arguments` and deferring through `process.nextTick`, guaranteeing the caller always sees an async callback. — [lib/view.js#L133-L162](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/view.js#L133-L162)
- Static file responses bypass `res.send` entirely — `sendfile()` attaches listeners for `directory`, `end`, `error`, `file`, `stream` plus `on-finished`, applies `options.headers` on the `headers` event, and then `file.pipe(res)`. — [lib/response.js#L923-L1010](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L923-L1010)
- The terminal output boundary for unhandled requests and errors is `finalhandler(req, res, { env, onerror: logerror })`, constructed per-dispatch unless the caller supplied its own callback. — [lib/application.js#L153-L158](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L153-L158)
- Settings inheritance for mounted apps is prototype-chain based, not copy based: `Object.setPrototypeOf(this.settings, parent.settings)` means later parent mutations are visible to already-mounted children. (inferred) — [lib/application.js#L115-L122](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L115-L122)

## Behavior traces

Four flows carry essentially all of Express's behaviour: application construction, request dispatch, response emission, and view rendering. Each is short and traceable by name — the framework is only three or four stack frames deep before control reaches an external package. The traces below name functions in call order as they appear in `lib/`.
- STARTUP: `require('express')` → `index.js` → `lib/express.js` → user calls `createApplication()` → `mixin(app, EventEmitter.prototype)` → `mixin(app, proto)` → `Object.create(req/res)` → `app.init()` → `app.defaultConfiguration()` → settings seeded and the `'mount'` listener attached → `Object.defineProperty(this, 'router', getter)` installs the lazy router. — [index.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/index.js) [lib/express.js#L36-L56](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/express.js#L36-L56) [lib/application.js#L59-L145](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L59-L145)
- STARTUP (server): `app.listen(...)` → `http.createServer(this)` → if the last arg is a function it is wrapped with `once()` and registered on the server's `'error'` event → `server.listen.apply(server, args)`; the returned value is the `http.Server`, not the app. — [lib/application.js#L598-L607](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L598-L607) [test/app.listen.js#L13-L26](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/app.listen.js#L13-L26)
- ROUTE REGISTRATION: `app.get('/x', fn)` → generated method sees `arguments.length > 1` → `this.route(path)` → `this.router.route(path)` (first access instantiates `new Router`) → `route.get(fn)`. — [lib/application.js#L471-L483](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L471-L483) [lib/application.js#L259-L261](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L259-L261) [lib/application.js#L70-L82](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L70-L82)
- DISPATCH: socket → `app(req, res)` closure → `app.handle` → `finalhandler(...)` built → `res.setHeader('X-Powered-By')` → `req.res`/`res.req` cross-links → both prototypes swapped → `res.locals` ensured → `this.router.handle(req, res, done)` hands off to the external router package. — [lib/express.js#L37-L39](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/express.js#L37-L39) [lib/application.js#L152-L182](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L152-L182)
- PRIMARY ACTION (`res.send('hi')`): typeof-string branch sets charset via `setCharset` or `this.type('html')` → `app.get('etag fn')` → length computed and `Content-Length` set unless `Transfer-Encoding` present → `etagFn(chunk, encoding)` → `this.set('ETag')` → `if (req.fresh) this.status(304)` → header stripping for 204/304/205 → `this.end(chunk, encoding)` (or bare `end()` for HEAD). — [lib/response.js#L126-L226](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L126-L226) [lib/utils.js#L225-L241](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/utils.js#L225-L241)
- JSON PATH: `res.json(obj)` → reads `json escape`/`json replacer`/`json spaces` → `stringify()` → sets `Content-Type: application/json` only if unset → delegates to `res.send(body)`, which then re-enters the string branch and applies the charset. — [lib/response.js#L236-L260](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L236-L260) [lib/response.js#L126-L150](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L126-L150)
- JSONP PATH: `res.jsonp` → `stringify()` → reads the callback from `req.query[app.get('jsonp callback name')]` → takes `callback[0]` if an array → strips everything outside `[^\[\]\w$.]` → sets `X-Content-Type-Options: nosniff` and `text/javascript` → escapes U+2028/U+2029 → wraps in the `/**/ typeof cb === 'function' && cb(...)` Rosetta-Flash mitigation → `res.send`. — [lib/response.js#L264-L318](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L264-L318) [test/res.jsonp.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/res.jsonp.js)
- RENDER PATH: `res.render(view, opts)` → `opts._locals = res.locals` → default callback `(err, str) => err ? req.next(err) : self.send(str)` → `app.render(name, opts, done)` → locals merged → cache consulted → `new View(name, { defaultEngine, root, engines })` → `view.lookup()` → `View#resolve` → `tryStat` → `tryRender(view, opts, done)` → `View#render` → engine → `process.nextTick` → `res.send(html)`. — [lib/response.js#L896-L920](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L896-L920) [lib/application.js#L522-L583](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L522-L583) [lib/view.js#L104-L192](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/view.js#L104-L192)
- ENGINE RESOLUTION: inside the `View` constructor, if `opts.engines[this.ext]` is missing, Express computes `mod = this.ext.slice(1)` and performs a dynamic `require(mod).__express`, throwing `Module "x" does not provide a view engine.` when that export is not a function. — [lib/view.js#L74-L88](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/view.js#L74-L88) [test/app.render.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/app.render.js)
- VIEW LOOKUP fallback: `View#resolve` first tries `<dir>/<file>` and, failing that, `<dir>/<basename>/index<ext>` — both via synchronous `fs.statSync` wrapped in `tryStat`. — [lib/view.js#L169-L204](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/view.js#L169-L204)
- FILE TRANSFER: `res.sendFile` → argument validation (throws on missing/non-string path, and on a relative path with no `root`) → `encodeURI(path)` → `opts.etag = app.enabled('etag')` → `send(req, pathname, opts)` → `sendfile(res, file, opts, cb)` → listeners registered → `file.pipe(res)`; the `onfinish` handler uses `setImmediate` to distinguish a completed stream from an aborted one. — [lib/response.js#L375-L430](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L375-L430) [lib/response.js#L923-L1010](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L923-L1010)
- DOWNLOAD: `res.download` → argument shuffling for the (filename|options|callback) overloads → `Content-Disposition` built from `basename(name || path)` → caller headers copied except `content-disposition` → `opts = Object.create(opts)` so user options stay on the prototype → `resolve(path)` when no `root` → `this.sendFile(fullPath, opts, done)`. — [lib/response.js#L437-L486](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L437-L486)
- REDIRECT: `res.redirect([status,] url)` → optional `depd` deprecation calls → `this.location(address)` runs `encodeUrl` and re-reads the header → `res.format({ text, html, default })` picks a body → `res.status(status)` → explicit `Content-Length` via `Buffer.byteLength` → `end()` (body skipped for HEAD). — [lib/response.js#L796-L870](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L796-L870)
- SUB-APP MOUNT: `app.use('/admin', subApp)` → duck-type check passes → `subApp.mountpath = '/admin'`, `subApp.parent = this` → `router.use(path, mounted_app)` → `subApp.emit('mount', this)` → the child's `'mount'` listener re-parents its `request`/`response`/`engines`/`settings` prototypes; at request time `mounted_app` restores `orig.request`/`orig.response` before calling `next`. — [lib/application.js#L190-L241](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L190-L241) [lib/application.js#L109-L123](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L109-L123) [test/app.use.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/app.use.js)
- ERROR EXIT: when no handler responds, control returns to the `done` built in `app.handle`; `finalhandler`'s `onerror` is bound to `logerror`, which `console.error`s unless `app.get('env') === 'test'`. — [lib/application.js#L153-L158](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L153-L158) [lib/application.js#L610-L618](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L610-L618)

## Testing signals

Testing is mocha + supertest with `after` for multi-assertion coordination, and it is unusually thorough for a codebase this size: 70 unit files under `test/` plus 18 acceptance files under `test/acceptance/`, totalling ~15,400 lines against ~5,500 lines of `lib/`. Unit files are named one-per-API (`res.send.js`, `req.host.js`, `app.param.js`), which makes the mapping from source method to test file mechanical. Acceptance tests do not use fixtures — they `require('../../examples/<name>')` and drive the real example apps, so `examples/` is effectively production test surface. Coverage runs through nyc in CI across the whole matrix, is merged with lcov and shipped to Coveralls.
- The runner is mocha with `--require test/support/env --check-leaks`, covering both `test/` and `test/acceptance/`; `--check-leaks` means any accidental global assignment fails the suite. — [package.json#L103](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/package.json#L103) [test/support/env.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/support/env.js)
- `test/support/env.js` forces `NODE_ENV=test` (which suppresses `logerror` output and keeps `view cache` disabled) and sets `NO_DEPRECATION=body-parser,express` to silence `depd` warnings. — [test/support/env.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/support/env.js) [lib/application.js#L615-L617](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L615-L617) [lib/application.js#L143-L145](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L143-L145)
- HTTP assertions run through `supertest` against the app function directly, without binding a port — e.g. `request(app).get('/').expect(200, done)`. — [test/exports.js#L59-L70](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/exports.js#L59-L70) [test/middleware.basic.js#L36-L42](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/middleware.basic.js#L36-L42) [package.json#L73](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/package.json#L73)
- `test/support/utils.js` provides the shared assertion vocabulary: `shouldHaveBody`, `shouldHaveHeader`, `shouldNotHaveBody`, `shouldNotHaveHeader`, `shouldSkipQuery` — with body comparison done on hex-encoded buffers. — [test/support/utils.js#L17-L40](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/support/utils.js#L17-L40)
- `test/support/tmpl.js` is a hand-rolled `$var.path` template engine used as the canonical view-engine double, avoiding a real engine dependency in most view tests. — [test/support/tmpl.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/support/tmpl.js) [test/app.render.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/app.render.js)
- The largest test files track the riskiest code: `app.router.js` (1217 lines), `res.sendFile.js` (913), `express.urlencoded.js` (828), `express.static.js` (815), `express.json.js` (755), `Router.js` (636), `res.send.js` (635). — [test/app.router.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/app.router.js) [test/res.sendFile.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/res.sendFile.js) [test/express.static.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/express.static.js) [test/res.send.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/res.send.js)
- Path-traversal behaviour is explicitly covered: `express.static.js` asserts handling of `/users/../todo.txt`, `/users/../../todo.txt` and the URL-encoded `/users/%2e%2e/%2e%2e/todo.txt`, and `res.sendFile.js` covers `fake/../name.txt` and `../name.txt` under a `root`. — [test/express.static.js#L83-L88](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/express.static.js#L83-L88) [test/express.static.js#L580-L590](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/express.static.js#L580-L590) [test/res.sendFile.js#L864-L900](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/res.sendFile.js#L864-L900)
- All 18 acceptance files import example apps rather than fixtures — `test/acceptance/auth.js` requires `../../examples/auth` and drives the full login/session/restricted flow including cookie extraction. — [test/acceptance/auth.js#L1-L10](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/acceptance/auth.js#L1-L10) [examples/auth/index.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/examples/auth/index.js)
- Seven of the 26 examples have no acceptance test: `online`, `route-middleware`, `search`, `session`, `static-files`, `view-constructor`, `view-locals` — leaving the custom-`View`-constructor extension point exercised only by the example itself. — [examples/README.md#L5-L28](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/examples/README.md#L5-L28) [test/acceptance/route-map.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/acceptance/route-map.js) [examples/view-constructor/index.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/examples/view-constructor/index.js)
- `test/exports.js` deliberately mutates `express.application`, `express.request` and `express.response` prototypes to prove the extension contract holds — meaning test ordering has real cross-file coupling. — [test/exports.js#L52-L70](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/exports.js#L52-L70)
- `test/regression.js` is a deliberately tiny guard file — currently one case asserting that throwing after `res.end()` still yields a clean 200 response. — [test/regression.js#L6-L20](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/regression.js#L6-L20)
- Coverage is produced per matrix cell (`npm run test-ci` → nyc lcovonly), uploaded as `coverage-node-<ver>-<os>` artifacts, then merged in a dedicated `coverage` job with `lcov -a` and pushed to Coveralls. — [.github/workflows/ci.yml#L74-L116](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/.github/workflows/ci.yml#L74-L116) [package.json#L104](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/package.json#L104)
- nyc excludes `examples`, `test` and `benchmarks` from instrumentation, so the acceptance tests contribute coverage for `lib/` only — and the `benchmarks` exclude refers to a directory that no longer exists in the tree. — [package.json#L104-L105](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/package.json#L104-L105)
- CodeQL scans `javascript` and `actions` on push/PR to master plus a weekly cron, with `paths-ignore: test`; OpenSSF Scorecard runs weekly and on branch-protection changes, publishing SARIF to code scanning. — [.github/workflows/codeql.yml#L15-L54](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/.github/workflows/codeql.yml#L15-L54) [.github/workflows/scorecard.yml#L6-L74](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/.github/workflows/scorecard.yml#L6-L74)
- Explicit `/* istanbul ignore next */` pragmas mark knowingly-uncovered branches, including `logerror`'s console path and the `stringify` escape default case. — [lib/application.js#L614-L616](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L614-L616) [lib/response.js#L1043-L1045](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L1043-L1045)

## Risk map

The security-sensitive surface concentrates in four places: proxy trust (which decides whether client-supplied `X-Forwarded-*` headers are believed), JSONP callback sanitisation, file transfer via `res.sendFile`/`res.download`, and dynamic `require()` inside the view system. Blast radius is dominated by `res.send` — `json`, `jsonp`, `sendStatus` and the default `render` callback all funnel through it, so any change there touches nearly every response. Coupling risks come from prototype mutation: `app.handle` re-prototypes live Node objects and mounting re-parents settings chains, so behaviour can change at a distance. Supply-chain posture is strong, but `.npmrc` disables lockfiles, so local installs are not reproducible.
- SECURITY — proxy trust: `req.ip`, `req.ips`, `req.protocol`, `req.secure` and `req.host` all believe client-supplied headers whenever `trust proxy fn` returns true; setting `trust proxy` to `true` makes `compileTrust` return a constant-true function, so `X-Forwarded-For`/`-Proto`/`-Host` become fully spoofable. — [lib/utils.js#L194-L215](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/utils.js#L194-L215) [lib/request.js#L297-L343](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/request.js#L297-L343) [lib/request.js#L418-L431](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/request.js#L418-L431)
- SECURITY — dynamic require: the `View` constructor derives a module name from the view's file extension and calls `require(mod)`. An application that renders a view name built from user input can drive module resolution, and the thrown errors leak which module was attempted. — [lib/view.js#L58-L88](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/view.js#L58-L88)
- SECURITY — JSONP: the callback is taken from `req.query` and filtered with `replace(/[^\[\]\w$.]/g, '')`; correctness of this allowlist plus the `/**/ typeof` wrapper and forced `X-Content-Type-Options: nosniff` are the only things standing between `res.jsonp` and reflected script injection. — [lib/response.js#L264-L318](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L264-L318)
- SECURITY — prototype keys: the `extended` query parser calls `qs.parse` with `allowPrototypes: true`, which is a deliberate compatibility choice that permits keys like `constructor` in parsed query objects. — [lib/utils.js#L267-L271](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/utils.js#L267-L271)
- SECURITY — file IO outside repo root: `res.sendFile` only enforces "absolute path or `root` option" and `encodeURI`; every actual containment check is delegated to the `send` package, so a `send` regression is an Express path-traversal regression. — [lib/response.js#L398-L420](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L398-L420) [package.json#L52](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/package.json#L52)
- SECURITY — information disclosure by default: `x-powered-by` is enabled in `defaultConfiguration` and `app.handle` sets the `X-Powered-By: Express` header on every response unless explicitly disabled. — [lib/application.js#L94-L95](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L94-L95) [lib/application.js#L160-L163](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L160-L163)
- SECURITY — cookie signing depends on a foreign property: `res.cookie` reads `this.req.secret`, which Express never sets; it throws `cookieParser("secret") required for signed cookies` if `cookie-parser` was not installed upstream, making signing silently middleware-order dependent. — [lib/response.js#L747-L755](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L747-L755)
- BLAST RADIUS — `res.send` is the shared exit for `res.json`, `res.jsonp`, `res.sendStatus` and the default `res.render` callback; the HEAD-vs-body, 204/304/205 header stripping, ETag and Content-Length rules all live in that one function. — [lib/response.js#L126-L226](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L126-L226) [lib/response.js#L236-L334](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L236-L334) [lib/response.js#L896-L920](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L896-L920)
- FRAGILE COUPLING — mutation of live Node objects: `app.handle` calls `Object.setPrototypeOf` on the actual `IncomingMessage`/`ServerResponse`, and `mounted_app` must manually restore the parent prototypes after a sub-app completes; forgetting that restore silently leaks the child's prototype into sibling middleware. — [lib/application.js#L168-L182](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L168-L182) [lib/application.js#L217-L226](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L217-L226)
- FRAGILE COUPLING — settings inheritance is by prototype chain (`Object.setPrototypeOf(this.settings, parent.settings)`), guarded by a string symbol `'@@symbol:trust_proxy_default'` that is deleted/redefined in two separate places to preserve back-compat. — [lib/application.js#L47](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L47) [lib/application.js#L100-L123](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L100-L123) [lib/application.js#L371-L380](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L371-L380)
- PERFORMANCE — synchronous file IO on the request path: `View#resolve` calls `fs.statSync` up to twice per lookup, and `view cache` is only auto-enabled when `NODE_ENV === 'production'`, so non-production deployments stat the filesystem on every render. — [lib/view.js#L169-L204](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/view.js#L169-L204) [lib/application.js#L143-L145](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L143-L145)
- PERFORMANCE — `req.query`, `req.host`, `req.protocol` and `req.ip` are non-memoised getters; each access re-parses. Middleware that reads `req.query` repeatedly pays the `qs`/`querystring` cost every time. (inferred) — [lib/request.js#L230-L241](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/request.js#L230-L241) [lib/request.js#L521-L527](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/request.js#L521-L527)
- LEGACY — `depd` deprecation paths remain live in `res.redirect` for missing URLs, non-string URLs and non-number statuses, meaning three invalid-argument shapes still produce a response instead of throwing. — [lib/response.js#L814-L840](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L814-L840) [lib/response.js#L17](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L17)
- LEGACY — `legacy.yml` still runs the full suite on Node 16 and 17 even though `package.json` declares `engines.node >= 18`; the two signals disagree about the supported floor. — [.github/workflows/legacy.yml#L30-L36](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/.github/workflows/legacy.yml#L30-L36) [package.json#L91-L93](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/package.json#L91-L93)
- REPRODUCIBILITY — `.npmrc` sets `package-lock=false`, and no lockfile is committed, so CI and local installs resolve dependency versions independently at install time; `min-release-age=7` is the mitigation. — [.npmrc](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/.npmrc) [.gitignore](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/.gitignore)
- SUBTLE API — `res.download` rebinds options with `opts = Object.create(opts)` so caller options live on the prototype rather than as own properties; any downstream code doing `Object.keys(opts)` or a spread will not see them. — [lib/response.js#L475-L480](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L475-L480)
- BREAKING-CHANGE HAZARD — `res.status` now throws `TypeError` for non-integers and `RangeError` outside 100–999, so any code path that computed a status dynamically can convert a soft bug into a thrown exception mid-response. — [lib/response.js#L65-L78](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L65-L78) [test/res.status.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/res.status.js)

## Extension points

Express is designed to be extended by mutation rather than by configuration. Three exported prototypes (`express.application`, `express.request`, `express.response`) are public and explicitly documented as mutable by the test suite. Beyond that there are four first-class registries — the template-engine map, the `view` constructor, the compiled-function settings (`etag`/`query parser`/`trust proxy`), and route params — plus mounting, which composes whole apps. The verb table is itself generated from Node's `http.METHODS`, so new HTTP methods appear automatically.
- `app.engine(ext, fn)` is the template-engine registry: it normalises the leading dot and stores into `this.engines[extension]`, which `View` consults before falling back to a dynamic `require`. — [lib/application.js#L280-L295](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L280-L295) [lib/view.js#L74-L88](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/view.js#L74-L88) [test/app.engine.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/app.engine.js)
- `app.set('view', CustomViewCtor)` swaps the entire view-resolution class — `app.render` reads `this.get('view')` and does `new View(name, { defaultEngine, root, engines })`, requiring only a `.path` property and a `.render(options, cb)` method. — [lib/application.js#L136](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L136) [lib/application.js#L548-L568](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L548-L568) [examples/view-constructor/index.js#L28-L30](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/examples/view-constructor/index.js#L28-L30) [examples/view-constructor/github-view.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/examples/view-constructor/github-view.js)
- The three compiler settings all short-circuit on a function argument — `compileETag`, `compileQueryParser` and `compileTrust` each begin with `if (typeof val === 'function') return val`, so any of them can be fully replaced by user code. — [lib/utils.js#L130-L215](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/utils.js#L130-L215) [test/config.js#L45-L62](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/config.js#L45-L62)
- `express.application`, `express.request` and `express.response` are exported prototypes and the test suite asserts that mutating them affects all subsequently created apps — the documented monkey-patch seam. — [lib/express.js#L62-L64](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/express.js#L62-L64) [test/exports.js#L52-L70](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/exports.js#L52-L70)
- The idiomatic per-app variant of that seam is mutating `app.response` / `app.request` rather than the global prototype, as `examples/mvc` does by defining `app.response.message = function(msg){...}`. — [examples/mvc/index.js#L22-L30](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/examples/mvc/index.js#L22-L30) [lib/express.js#L44-L51](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/express.js#L44-L51)
- The HTTP verb table is not hardcoded — `exports.methods` maps `require('node:http').METHODS` to lowercase and `methods.forEach` generates both `app[method]` and the per-method registration used by `app.all`. — [lib/utils.js#L29](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/utils.js#L29) [lib/application.js#L471-L501](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L471-L501)
- `app.param(name, fn)` accepts either a single name or an array (recursing per element) and forwards to `this.router.param`, giving a named hook for pre-resolving route parameters. — [lib/application.js#L308-L322](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L308-L322) [test/app.param.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/app.param.js) [examples/params/index.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/examples/params/index.js)
- `app.use(path, subApp)` is the composition primitive: any object with `.handle` and `.set` is treated as a mountable app, receives `mountpath`/`parent`, and emits `'mount'` — which applications may listen to for their own inheritance logic. — [lib/application.js#L205-L241](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L205-L241) [examples/multi-router/index.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/examples/multi-router/index.js)
- `express.Router` and `express.Route` are re-exported from the `router` package, so custom routers can be constructed independently and mounted with `app.use`. — [lib/express.js#L70-L71](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/express.js#L70-L71) [test/Router.js#L7-L30](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/Router.js#L7-L30) [examples/multi-router/index.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/examples/multi-router/index.js)
- `res.format(obj)` is a per-response dispatch table keyed by MIME type or extension name, with an optional `default` key — the content-negotiation extension point, and itself the mechanism behind `res.redirect`'s body selection. — [lib/response.js#L573-L604](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L573-L604) [lib/response.js#L841-L858](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L841-L858) [examples/content-negotiation/index.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/examples/content-negotiation/index.js)
- `app.locals` and `res.locals` are the two data-injection points into rendering; `app.render` merges them in a fixed order with caller options, and `app.locals.settings` is pre-wired so templates can read app settings. — [lib/application.js#L125-L131](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L125-L131) [lib/application.js#L536-L540](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L536-L540) [examples/view-locals/index.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/examples/view-locals/index.js)
- Route organisation patterns are demonstrated as extension idioms rather than framework features: `examples/route-map` builds routes from a nested object literal, and `examples/mvc/lib/boot.js` auto-loads controllers into the app. — [examples/route-map/index.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/examples/route-map/index.js) [examples/mvc/lib/boot.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/examples/mvc/lib/boot.js) [examples/mvc/index.js#L74](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/examples/mvc/index.js#L74)
- Middleware re-exports (`express.json`, `express.raw`, `express.text`, `express.urlencoded`, `express.static`) are plain property assignments on the module export, so the whole body-parsing layer can be swapped by replacing the dependency. — [lib/express.js#L77-L81](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/express.js#L77-L81) [test/exports.js#L11-L35](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/exports.js#L11-L35)
- `res.type`/`res.contentType` and `normalizeType`/`normalizeTypes` centralise MIME handling through `mime-types`, so extension-name support is inherited rather than enumerated in this repo. — [lib/response.js#L507-L513](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L507-L513) [lib/utils.js#L61-L80](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/utils.js#L61-L80)

## Contributor handoff

This is a mature, style-conservative library where the diff bar is high and behaviour is pinned by a very large test suite. Read `lib/express.js`, `lib/application.js`, then the method you are changing in `lib/response.js` or `lib/request.js`; the test file name mirrors the API name, so `res.send` changes belong in `test/res.send.js`. The single biggest trap is that `test/acceptance/*` boots the real apps in `examples/`, so editing an example breaks tests in a different directory. Dependencies are installed fresh (no lockfile, no committed `node_modules`), so `npm install` is a required first step.
- READ FIRST, in order: `lib/express.js` (the factory and the export surface), `lib/application.js` (settings, `handle`, `use`, `render`), then the specific prototype file you are touching — `lib/response.js` or `lib/request.js`. — [lib/express.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/express.js) [lib/application.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js) [lib/response.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js) [lib/request.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/request.js)
- SETUP: `node_modules/` is absent and `.npmrc` sets `package-lock=false`, so you must run `npm install` before any test run; there is no build step to run afterwards. — [.npmrc](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/.npmrc) [.gitignore](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/.gitignore) [package.json#L101-L107](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/package.json#L101-L107)
- TESTS TO RUN: `npm test` for the full mocha suite (`test/` + `test/acceptance/`), `npm run test-cov` for an HTML coverage report, and `npm run lint` before committing — CI runs lint as a separate required job. — [package.json#L101-L107](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/package.json#L101-L107) [.github/workflows/ci.yml#L26-L42](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/.github/workflows/ci.yml#L26-L42)
- LINT RULES that bite: 2-space `indent` with `MemberExpression: off`, `eqeqeq` with `allow-null`, `eol-last`, `no-trailing-spaces`, and a `no-restricted-globals` rule banning the global `Buffer` — you must write `const { Buffer } = require('node:buffer')`. — [.eslintrc.yml](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/.eslintrc.yml) [lib/response.js#L36](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L36) [lib/utils.js#L25](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/utils.js#L25)
- STYLE: `lib/` is ES5-flavoured — `var` declarations, `'use strict'` at the top of every file, JSDoc blocks with explicit `@public`/`@private` on every exported member. Match it rather than modernising opportunistically. — [lib/application.js#L1-L50](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L1-L50) [lib/request.js#L1-L40](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/request.js#L1-L40)
- NODE BUILTINS are always imported with the `node:` prefix (`node:http`, `node:path`, `node:fs`, `node:net`, `node:querystring`, `node:buffer`, `node:events`) — follow this in new code and in tests. — [lib/application.js#L19-L24](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L19-L24) [lib/view.js#L18-L20](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/view.js#L18-L20) [test/support/utils.js#L7-L8](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/support/utils.js#L7-L8)
- TEST NAMING is a strict mirror of the API: `test/res.<method>.js`, `test/req.<getter>.js`, `test/app.<method>.js`. A new `res.foo` needs a new `test/res.foo.js`, not an addition to a catch-all file. — [test/res.send.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/res.send.js) [test/req.host.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/req.host.js) [test/app.param.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/app.param.js)
- TRAP — cross-directory coupling: the 18 files in `test/acceptance/` `require('../../examples/<name>')`, so an edit to an example app fails tests that do not mention it. Check `test/acceptance/` before touching `examples/`. — [test/acceptance/auth.js#L1](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/acceptance/auth.js#L1) [test/acceptance/mvc.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/acceptance/mvc.js) [examples/auth/index.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/examples/auth/index.js)
- TRAP — `test/exports.js` permanently mutates `express.application`, `express.request` and `express.response` for the remainder of the mocha process; new global-prototype tests can interact with it. — [test/exports.js#L52-L70](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/exports.js#L52-L70)
- TRAP — `--check-leaks` is enabled, so any implicit global created during a test (including in `lib/`) fails the run with a leak error rather than an assertion error. — [package.json#L103](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/package.json#L103)
- TRAP — the test env sets `NO_DEPRECATION=body-parser,express`, so `depd` warnings you add will be invisible locally; assert on behaviour, not on console output. — [test/support/env.js](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/test/support/env.js) [lib/response.js#L17](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L17)
- SAFE EDIT BOUNDARY — routing, body parsing and static serving are NOT in this repo. Bugs in path matching, `req.params`, `req.body` or directory traversal belong to `router`, `body-parser`, `send` and `serve-static`. — [package.json#L27-L56](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/package.json#L27-L56) [lib/express.js#L19-L81](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/express.js#L19-L81)
- HIGH-CAUTION FILES: `lib/response.js` `res.send` (every response path), `lib/application.js` `app.handle` (every request), and `lib/utils.js` `compileTrust` (all proxy-derived security decisions). Changes here need matching test additions in `test/res.send.js`, `test/app.router.js` and `test/req.ip.js`. — [lib/response.js#L126-L226](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L126-L226) [lib/application.js#L152-L182](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/application.js#L152-L182) [lib/utils.js#L194-L215](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/utils.js#L194-L215)
- PUBLISHED SURFACE is limited by `package.json` `files` to `LICENSE`, `Readme.md`, `index.js`, `lib/` — a new runtime file must live under `lib/` to be shipped at all. — [package.json#L95-L100](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/package.json#L95-L100)
- CHANGELOG convention: `History.md` (127KB) is the maintained release log; commit subjects follow conventional-commit style with a PR reference, e.g. `fix(res.send): preserve ETag generation with Transfer-Encoding (#7459)`. (inferred) — [History.md](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/History.md) [lib/response.js#L172-L182](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/lib/response.js#L172-L182)
- DEPENDENCY POLICY: dependabot is configured monthly for both npm and GitHub Actions and explicitly ignores all `semver-major` updates, so do not propose a major bump as part of a feature change. — [.github/dependabot.yml#L12-L24](https://github.com/expressjs/express/blob/9a34acf03cb818ff3f8bc40e44176e277a25cbb9/.github/dependabot.yml#L12-L24)

Source repository: https://github.com/expressjs/express
Full evidence corpus: https://github.com/Codevetter/codevetter/tree/main/benchmarks/repo-unpacks/express.json

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
