This page gets you from a fresh clone to a verified change. Express has no build step, no transpiler, and no code generation: the JavaScript in lib/ is exactly what ships to npm, so the edit-test loop is seconds long.

Setup

You need Node.js 18 or newer ("engines": { "node": ">= 18" } in package.json). Then:

git clone https://github.com/expressjs/express.git
cd express
npm install
npm test

npm test runs the whole suite. The scripts defined in package.json:

package.json
"scripts": {
  "lint": "eslint .",
  "lint:fix": "eslint . --fix",
  "test": "mocha --require test/support/env --reporter spec --check-leaks test/ test/acceptance/",
  "test-ci": "nyc --exclude examples --exclude test --exclude benchmarks --reporter=lcovonly --reporter=text npm test",
  "test-cov": "nyc --exclude examples --exclude test --exclude benchmarks --reporter=html --reporter=text npm test",
  "test-tap": "mocha --require test/support/env --reporter tap --check-leaks test/ test/acceptance/"
}

Note the --require test/support/env in the test command -- it forces NODE_ENV=test and silences deprecation noise before any test loads:

test/support/env.js
process.env.NODE_ENV = 'test';
process.env.NO_DEPRECATION = 'body-parser,express';

To run a single test file while iterating, use mocha directly with the same flag:

npx mocha --require test/support/env test/res.send.js

Repo tour

  • index.js -- one line, re-exports lib/express.js.
  • lib/ -- the entire runtime, six files. Read them in this order: express.js (factory, 81 lines), application.js (app object), request.js and response.js (prototype extensions), view.js (templates), utils.js (setting compilers).
  • test/ -- one file per API member, named after what it tests: res.send.js, req.query.js, app.use.js, Router.js, Route.js. Fixtures live in test/fixtures/, shared helpers in test/support/.
  • test/acceptance/ -- one test file per example app; test/acceptance/cookies.js drives examples/cookies end to end.
  • examples/ -- 26 runnable apps, each a directory with an index.js. Run any of them with node examples/content-negotiation.
  • benchmarks/, Charter.md, Code-Of-Conduct.md, etc. -- project governance and tooling, not runtime code.

Your first change

A low-risk, well-contained first contribution is an example plus its acceptance test, because the pair is self-verifying and touches no runtime code. Pick an example -- say examples/params/index.js -- and its mirror test/acceptance/params.js. Change behavior in the example, watch the acceptance test fail, update the test, watch it pass:

node examples/params            # manual check on http://localhost:3000
npx mocha --require test/support/env test/acceptance/params.js

For a runtime change, the same loop applies with the matching unit file. Every public member of req, res, and app has a dedicated test file, so the blast radius of an edit in lib/response.js is visible by running test/res.*.js.

Before opening a PR:

npm run lint
npm test

The test command runs with --check-leaks, so a test that leaks globals fails the suite even if its assertions pass.

Where the tests are the specification

Express's behavioral guarantees live in test/, not in prose. When you need to know what res.send promises about ETags, or when a 304 strips the body, the answer is an it(...) title in test/res.send.js. The testing page shows how those tests are built and how to write one.

Sources: package.json, test/support/env.js, examples/README.md · last synced 2026-07-27 · a371447 · version 5.2.1