Open-Source Wikis

/

Express

/

How to contribute

/

Patterns and conventions

expressjs/express

Patterns and conventions

Active contributors: ulisesgascon, jonchurch, wesleytodd

Express's coding style is older and tighter than most modern Node projects. New code should match the surrounding file rather than introduce a new dialect.

Module style

  • CommonJS only. Files use var foo = require('foo') and module.exports = .... No ES modules in source.
  • Strict mode. Every file begins with 'use strict';.
  • License banner. Each file starts with the standard banner naming TJ Holowaychuk, Roman Shtylman, and Douglas Christopher Wilson.
  • JSDoc. Public methods have JSDoc with @param, @return, and @public / @private / @api annotations. Several response helpers have full usage examples in their JSDoc.

Variable declarations

  • var everywhere. The codebase predates let/const and is consistent in not using them, except in a few newer files where const is used for module-level constants (e.g., the Buffer import in lib/response.js).
  • The ESLint config does not forbid let/const, but PRs that mix styles within a file are typically asked to align with the file.

Indentation and formatting

.eslintrc.yml:

  • 2-space indent with MemberExpression: off and SwitchCase: 1.
  • eol-last: error (files end with a newline).
  • no-trailing-spaces: error.
  • eqeqeq: [error, allow-null]=== everywhere except == null is permitted.
  • no-unused-vars with args: none and ignoreRestSiblings: true.
  • no-restricted-globals bans the global Buffer. Use const { Buffer } = require('node:buffer') (or the import equivalent in tests).

Run npm run lint to check, npm run lint:fix to auto-fix.

Built-in modules

Use node: prefixes for built-ins: require('node:http'), require('node:path'), require('node:querystring'), etc. This was tightened up during the 5.x cleanup pass.

Error handling

  • Throw TypeError for programmer errors (wrong argument types). Examples in lib/response.js: res.sendFile throws TypeError when path is missing or not a string.
  • Throw RangeError for invalid numeric ranges (e.g., res.status outside [100, 999]).
  • Use http-errors (createError) for HTTP-status errors: e.g., res.format calls next(createError(406, ...)) when content negotiation fails.
  • Pass errors through next(err) in middleware-style functions.

Errors should include enough context to be debugged from logs: Failed to lookup view "${name}" in views ${dirs} is a good template.

Deprecation

lib/response.js uses the depd-style deprecate helper for soft deprecations (e.g., res.redirect warning when the URL is undefined). Internal-only deprecations are not announced to users; user-visible ones go in History.md.

Logging

Use the debug package, namespaced as express:<subsystem>:

var debug = require('debug')('express:application');
debug('booting in %s mode', env);

Never console.log at production paths. The only console.error in core (logerror in lib/application.js) is for the default error handler, and it short-circuits in env === 'test'.

Prototypes over classes

Express predates ES classes and consistently uses prototype-based inheritance:

  • var req = Object.create(http.IncomingMessage.prototype) in lib/request.js.
  • var res = Object.create(http.ServerResponse.prototype) in lib/response.js.
  • The application prototype is a plain object: var app = exports = module.exports = {}; in lib/application.js.

When extending, copy methods onto these prototypes via direct assignment (req.foo = function ...) or via merge-descriptors. Resist refactoring to ES classes — it would change the public extension contract.

Argument flexibility

Many helpers accept multiple call signatures (res.send(body), res.send(buffer), res.json(obj), res.cookie(name, value, opts), res.download(path[, filename][, opts][, cb])). The convention is:

  1. Detect the type of each argument with typeof checks.
  2. Reassign locals to canonical positions.
  3. Run the body of the function against the canonical form.

res.download in lib/response.js is a representative example.

Performance-sensitive idioms

  • Array.prototype.slice.call(arguments, n) instead of rest parameters in some hot paths.
  • Buffer.byteLength(chunk, encoding) for length-without-allocation.
  • ETag is only generated when there is no existing ETag and the body is small; otherwise Buffer.from is used to compute the length and the ETag in one pass.
  • Object.create(null) for objects used as plain maps (e.g., app.cache, app.engines, app.settings, req.query when parser is disabled).

File-level structure

A typical lib/*.js file is organised as:

  1. License banner.
  2. 'use strict';.
  3. // Module dependencies. block of require()s.
  4. // Module variables. of internal helpers.
  5. The exported prototype/object/factory.
  6. Method definitions, ordered by public surface first, then @private helpers.
  7. Internal helper functions at the bottom.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Patterns and conventions – Express wiki | Factory