Jesse A. Green / notebook

Express User Guide

Generated May 2026 from expressjs/express at 6340c1e

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

1. Quick Start

Express is a fast, unopinionated, minimalist web framework for Node.js. It provides a thin layer of fundamental web application features — routing, middleware, templating, and HTTP utilities — without obscuring the Node.js platform underneath.

Key features:

  • Robust routing with support for parameters, pattern matching, and HTTP method handlers
  • Middleware pipeline architecture for composable request/response processing
  • Built-in body parsing (json, urlencoded, raw, text) and static file serving
  • Template engine integration (EJS, Handlebars, Pug, and any engine exposing __express)
  • Content negotiation via Accept header inspection
  • Cookie handling with optional signing
  • Proxy trust configuration for X-Forwarded-* header support
  • Sub-application mounting for modular architectures

Prerequisites: Node.js 18 or later

Install and run:

npm install express

Minimal application:

const express = require('express');
const app = express();

app.get('/', (req, res) => {
  res.send('Hello World!');
});

app.listen(3000, () => {
  console.log('Server listening on port 3000');
});
Server listening on port 3000

Tip

Pass an object or array to res.send() and Express automatically serializes it as JSON with the correct Content-Type header — no need to call res.json() explicitly.

What to try next: see Configuration for application settings, Core Usage for routing and middleware, and Common Tasks for real-world recipes.

2. Installation

System Requirements

Requirement Minimum
Node.js >= 18
npm Any version bundled with Node 18+
OS Any platform supported by Node.js

Install Methods

npm

npm install express

yarn

yarn add express

pnpm

pnpm add express

Post-Install Verification

node -e "console.log(require('express/package.json').version)"
5.2.1

Important

Express 5 requires Node.js 18 or later. If you are running an older Node.js version, you must upgrade before installing.

Dependencies

Express 5.2.1 ships with these production dependencies (installed automatically):

Dependency Purpose
body-parser ^2.2.1 Request body parsing (JSON, URL-encoded, raw, text)
router ^2.2.0 HTTP routing engine
serve-static ^2.2.0 Static file serving middleware
send ^1.1.0 File streaming for res.sendFile
accepts ^2.0.0 Content negotiation
cookie ^0.7.1 Cookie parsing/serialization
cookie-signature ^1.2.1 Signed cookie support
qs ^6.14.2 Extended query string parsing
proxy-addr ^2.0.7 Trusted proxy address resolution
debug ^4.4.0 Debug logging (DEBUG=express:*)
finalhandler ^2.1.0 Default error handling
mime-types ^3.0.0 MIME type resolution
etag ^1.8.1 ETag generation
fresh ^2.0.0 HTTP cache freshness checks
http-errors ^2.0.0 HTTP error creation
depd ^2.0.0 Deprecation notices
content-disposition ^1.0.0 Content-Disposition header generation
content-type ^1.0.5 Content-Type header parsing
encodeurl ^2.0.0 URL encoding
escape-html ^1.0.3 HTML entity escaping
merge-descriptors ^2.0.0 Object descriptor merging
on-finished ^2.4.1 Response finish detection
once ^1.4.0 One-time callback wrapper
parseurl ^1.3.3 URL parsing with caching
range-parser ^1.2.1 Range header parsing
statuses ^2.0.1 HTTP status code utilities
type-is ^2.0.1 Content-Type checking
vary ^1.1.2 Vary header management

Install Troubleshooting

Q: npm install fails with permission errors

Run npm install express without sudo. If your global node_modules has permission issues, configure npm to use a user-level prefix: npm config set prefix ~/.npm-global.

Q: Error: Cannot find module 'express'

Ensure you ran npm install in the project directory (where package.json lives), not globally.

Q: Module version conflicts with other packages

Delete node_modules and package-lock.json, then run npm install again to get a clean dependency tree.

3. Configuration

Express applications are configured via app.set(setting, value). Settings are inherited by mounted sub-apps.

Application Settings Reference

Setting Default Values Effect
env NODE_ENV or 'development' Any string Controls environment mode; 'production' enables view caching
etag 'weak' 'weak', 'strong', false, or function ETag generation strategy
jsonp callback name 'callback' Any string Query parameter name for JSONP responses
json escape N/A Boolean Escape <, >, & in JSON responses
json replacer N/A Function Custom JSON.stringify replacer
json spaces N/A Number JSON pretty-print indentation
query parser 'simple' 'simple', 'extended', false, or function Query string parser (simple = Node built-in, extended = qs library)
subdomain offset 2 Number Number of dot-separated parts to remove for req.subdomains
trust proxy false Boolean, string, number, array, or function Trust X-Forwarded-* headers from proxies
views ./views (resolved from CWD) String or array of strings Template view directory
view cache true in production Boolean Cache compiled template lookups
view engine N/A String (e.g., 'ejs', 'pug') Default template engine extension
view View (built-in) Constructor Custom view class
x-powered-by true Boolean Send X-Powered-By: Express header
case sensitive routing false Boolean Treat /Foo and /foo as different routes
strict routing false Boolean Treat /foo and /foo/ as different routes

Warning

The query parser setting defaults to 'simple' in Express 5 (using Node's built-in querystring module), which does not support nested objects. If your application relies on nested query parameters like ?user[name]=tobi, set app.set('query parser', 'extended').

Common Configuration Patterns

const app = express();

// Production-ready baseline
app.set('trust proxy', 1);          // trust first proxy hop
app.set('etag', 'strong');          // strong ETags for caching
app.set('query parser', 'extended'); // nested query object support
app.disable('x-powered-by');       // hide framework fingerprint

// Template engine setup
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));

Environment Variable

Variable Purpose
NODE_ENV Sets app.get('env'). When set to 'production', view caching is enabled automatically.

Tip

Use app.enable('setting') and app.disable('setting') as shorthand for app.set('setting', true/false). Check state with app.enabled('setting') or app.disabled('setting').

See the Admin Guide for full configuration reference with internals and compilation details.

4. Core Usage

Creating an Application

const express = require('express');
const app = express();

The express() factory returns a function that doubles as an HTTP request handler. The returned object inherits from EventEmitter and exposes the full application API.

Routing

Routes map HTTP methods and URL paths to handler functions.

Basic Route Methods

app.get('/users', (req, res) => {
  res.json([{ name: 'Tobi' }]);
});

app.post('/users', (req, res) => {
  res.status(201).json({ created: true });
});

app.put('/users/:id', (req, res) => {
  res.json({ updated: req.params.id });
});

app.delete('/users/:id', (req, res) => {
  res.sendStatus(204);
});

Express supports all HTTP methods recognized by Node.js (get, post, put, delete, patch, options, head, and more). Use app.all() to match every method on a path:

app.all('/secret', (req, res, next) => {
  console.log('Accessing the secret section...');
  next();
});

Route Parameters

app.get('/user/:id', (req, res) => {
  res.send(`User ${req.params.id}`);
});

// Range parameters
app.get('/users/:from-:to', (req, res) => {
  res.json({ from: req.params.from, to: req.params.to });
});

Route Objects

Use app.route() to chain handlers for a single path:

app.route('/book')
  .get((req, res) => res.send('Get a book'))
  .post((req, res) => res.send('Add a book'))
  .put((req, res) => res.send('Update the book'));

Parameter Pre-Processing

Use app.param() to run logic before route handlers when a named parameter is present:

app.param('user', (req, res, next, id) => {
  // load user from database
  req.user = users[id];
  next();
});

app.get('/user/:user', (req, res) => {
  res.json(req.user);
});

Note

app.param() accepts an array of parameter names: app.param(['id', 'page'], callback).

Middleware

Middleware functions have access to the request, response, and the next function in the pipeline.

Application-Level Middleware

// Runs on every request
app.use((req, res, next) => {
  console.log(`${req.method} ${req.path}`);
  next();
});

// Runs only for paths starting with /api
app.use('/api', (req, res, next) => {
  // authenticate API requests
  next();
});

Built-In Middleware

Express 5 ships five built-in middleware functions:

Middleware Usage Purpose
express.json() app.use(express.json()) Parse JSON request bodies
express.urlencoded() app.use(express.urlencoded()) Parse URL-encoded form bodies
express.raw() app.use(express.raw()) Parse raw binary request bodies
express.text() app.use(express.text()) Parse plain text request bodies
express.static(root) app.use(express.static('public')) Serve static files from a directory
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static('public'));

Important

Body parsers must be mounted before route handlers that read req.body. Without express.json(), req.body will be undefined for JSON requests.

Error-Handling Middleware

Error-handling middleware takes four arguments. Express recognizes the 4-argument signature as an error handler:

app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});

Warning

The error handler must have exactly four parameters (err, req, res, next) even if you do not use next. Omitting any parameter changes the function signature and Express will not recognize it as an error handler.

Request Object (req)

Property/Method Returns Description
req.params Object Named route parameters
req.query Object Parsed query string (depends on query parser setting)
req.body Any Parsed request body (requires body-parser middleware)
req.get(header) / req.header(header) String Request header value (case-insensitive)
req.is(type) String/false/null Check if Content-Type matches type
req.accepts(types) String/false Content negotiation based on Accept header
req.acceptsCharsets(...charsets) String/Array Check Accept-Charset
req.acceptsEncodings(...encodings) String/Array Check Accept-Encoding
req.acceptsLanguages(...langs) String/Array Check Accept-Language
req.range(size, [options]) Array/number Parse Range header
req.ip String Remote IP (respects trust proxy)
req.ips Array Proxy chain IPs when trust proxy is set
req.hostname String Hostname from Host header (respects trust proxy)
req.host String Host header value (includes port if present)
req.path String URL pathname
req.protocol String 'http' or 'https' (respects trust proxy)
req.secure Boolean true if req.protocol === 'https'
req.subdomains Array Subdomain array (respects subdomain offset)
req.fresh Boolean Response cache is still valid
req.stale Boolean Response cache is expired
req.xhr Boolean true if X-Requested-With: XMLHttpRequest

Response Object (res)

Method Description
res.status(code) Set HTTP status code (chainable). Must be integer 100–999.
res.send(body) Send response (string → HTML, object/array → JSON, Buffer → binary)
res.json(obj) Send JSON response
res.jsonp(obj) Send JSON with JSONP callback support
res.sendStatus(code) Set status and send status message as body
res.sendFile(path, [options], [callback]) Stream a file with Content-Type auto-detection
res.download(path, [filename], [options], [callback]) Prompt file download (sets Content-Disposition)
res.redirect([status], url) Redirect (default 302)
res.render(view, [locals], [callback]) Render a template
res.type(type) / res.contentType(type) Set Content-Type
res.format(obj) Content negotiation response
res.set(field, [value]) / res.header(field, [value]) Set response header(s)
res.get(field) Get response header value
res.append(field, value) Append to response header
res.cookie(name, value, [options]) Set a cookie
res.clearCookie(name, [options]) Clear a cookie
res.location(url) Set Location header
res.links(links) Set Link header
res.vary(field) Add Vary header
res.locals Object

Template Rendering

app.set('view engine', 'ejs');
app.set('views', './views');

app.get('/', (req, res) => {
  res.render('index', { title: 'Home', users: ['Tobi', 'Loki'] });
});

Register custom engine mappings with app.engine():

app.engine('html', require('ejs').renderFile);

Note

Express auto-requires template engines by file extension. For .ejs files, it calls require('ejs').__express. Use app.engine() only when mapping a non-standard extension or when the engine lacks __express.

Sub-Applications (Mounting)

Mount an Express app as middleware on another app:

const admin = express();
admin.get('/', (req, res) => {
  res.send('Admin homepage');
});

const app = express();
app.use('/admin', admin);

Mounted apps inherit trust proxy, request/response prototypes, engines, and settings from their parent. The mount event fires on the child app when mounted. admin.mountpath returns '/admin'.

The Express Router

Use express.Router() to create modular, mountable route handlers:

const router = express.Router();

router.get('/', (req, res) => {
  res.json({ version: 1 });
});

router.get('/users', (req, res) => {
  res.json([]);
});

app.use('/api/v1', router);

5. Common Tasks

Serve Static Files

app.use(express.static('public'));
// Files in ./public/ are now served at the root URL
// e.g., public/css/style.css → GET /css/style.css

Mount at a virtual path prefix:

app.use('/static', express.static('public'));
// public/css/style.css → GET /static/css/style.css

Parse JSON Request Bodies

app.use(express.json());

app.post('/api/data', (req, res) => {
  console.log(req.body); // parsed JSON object
  res.json({ received: true });
});

Handle Form Submissions

app.use(express.urlencoded({ extended: true }));

app.post('/login', (req, res) => {
  const { username, password } = req.body;
  // authenticate...
  res.redirect('/dashboard');
});

Set and Read Cookies

app.get('/', (req, res) => {
  res.cookie('rememberme', '1', {
    maxAge: 900000,
    httpOnly: true
  });
  res.send('Cookie set');
});

To clear a cookie:

res.clearCookie('rememberme', { path: '/' });

Tip

The maxAge option is in milliseconds and automatically generates an expires date. Express converts maxAge to seconds internally for the Set-Cookie header.

File Downloads

app.get('/download', (req, res) => {
  res.download('/path/to/report.pdf', 'report.pdf', (err) => {
    if (err) {
      // handle error, but check if headers were already sent
      if (!res.headersSent) {
        res.status(500).send('Download failed');
      }
    }
  });
});

Content Negotiation

app.get('/data', (req, res) => {
  res.format({
    'text/html': () => {
      res.send('<p>Hello</p>');
    },
    'application/json': () => {
      res.json({ message: 'Hello' });
    },
    'text/plain': () => {
      res.send('Hello');
    },
    default: () => {
      res.status(406).send('Not Acceptable');
    }
  });
});

Build a REST API

const express = require('express');
const app = express();

app.use(express.json());

const users = [
  { id: 1, name: 'Tobi' },
  { id: 2, name: 'Loki' }
];

app.get('/api/users', (req, res) => {
  res.json(users);
});

app.get('/api/user/:name/repos', (req, res) => {
  const user = users.find(u => u.name === req.params.name);
  if (!user) return res.sendStatus(404);
  res.json({ user: user.name, repos: [] });
});

app.listen(3000);

Session Management

const session = require('express-session');

app.use(session({
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: false
}));

app.get('/login', (req, res) => {
  req.session.user = { name: 'tobi' };
  res.send('Logged in');
});

Virtual Hosts

const vhost = require('vhost');

const api = express();
api.get('/', (req, res) => res.send('API'));

app.use(vhost('api.example.com', api));

Multi-Router Architecture

const apiV1 = express.Router();
apiV1.get('/users', (req, res) => res.json({ version: 1 }));

const apiV2 = express.Router();
apiV2.get('/users', (req, res) => res.json({ version: 2 }));

app.use('/api/v1', apiV1);
app.use('/api/v2', apiV2);

Custom Error Pages

// 404 handler (after all routes)
app.use((req, res) => {
  res.status(404).render('404', { url: req.originalUrl });
});

// Error handler
app.use((err, req, res, next) => {
  const status = err.status || 500;
  res.status(status);

  if (app.get('env') === 'development') {
    res.render('500', { error: err });
  } else {
    res.render('500', { error: {} });
  }
});

HTTPS Server

const https = require('node:https');
const fs = require('node:fs');
const express = require('express');
const app = express();

const options = {
  key: fs.readFileSync('server.key'),
  cert: fs.readFileSync('server.cert')
};

https.createServer(options, app).listen(443);

Note

app.listen() creates an HTTP server internally. For HTTPS, create the server with https.createServer() and pass the Express app as the callback.

User Authentication Pattern

function restrict(req, res, next) {
  if (req.session.user) {
    next();
  } else {
    req.session.error = 'Access denied!';
    res.redirect('/login');
  }
}

app.get('/restricted', restrict, (req, res) => {
  res.send('Protected content');
});

Online User Tracking

let onlineCount = 0;

app.use((req, res, next) => {
  if (!req.session.online) {
    req.session.online = true;
    onlineCount++;
  }
  next();
});

app.get('/', (req, res) => {
  res.send(`${onlineCount} users online`);
});

6. Troubleshooting

Diagnostic Decision Tree

  1. App won't start? → Check Node.js version (node -v must be >= 18) and that express is in node_modules
  2. Route not matching? → Check method, path, and middleware order. Routes are matched in registration order.
  3. req.body is undefined? → Mount express.json() or express.urlencoded() before your routes
  4. Static files not serving? → Verify the directory path passed to express.static() is correct relative to where you run node
  5. Template not found? → Check views setting and view engine setting

Error: app.use() requires a middleware function

Cause: You passed a non-function argument to app.use() without a path prefix, or passed undefined (often from a missing/misspelled require).

Solution:

  1. Verify the middleware module is installed: npm ls <module-name>
  2. Check spelling of the require() call
  3. Ensure the module exports a function

Prevention: Always verify that imported middleware is a function before passing it to app.use().

Error: callback function required

Cause: app.engine() was called with a non-function second argument.

Solution:

// Wrong
app.engine('html', 'ejs');

// Correct
app.engine('html', require('ejs').renderFile);

Error: No default engine was specified and no extension was provided.

Cause: res.render('viewname') was called without setting a view engine and without providing a file extension.

Solution:

// Option 1: Set default engine
app.set('view engine', 'ejs');
res.render('index'); // looks for views/index.ejs

// Option 2: Provide extension explicitly
res.render('index.ejs');

Error: Failed to lookup view "..." in views directory "..."

Cause: The template file does not exist in the configured views directory.

Solution:

  1. Check that app.set('views', path) points to the correct directory
  2. Verify the template file exists with the correct extension
  3. If using multiple view directories, pass an array: app.set('views', [dir1, dir2])

Error: path must be absolute or specify root to res.sendFile

Cause: res.sendFile() received a relative path without the root option.

Solution:

// Option 1: Absolute path
res.sendFile(path.join(__dirname, 'files', 'report.pdf'));

// Option 2: Use root option
res.sendFile('report.pdf', { root: path.join(__dirname, 'files') });

Error: name argument is required to req.get

Cause: req.get() or req.header() was called without a header name argument, or the argument was not a string.

Solution: Always pass a string: req.get('Content-Type').

Error: Invalid status code: ... Status code must be an integer.

Cause: res.status() received a non-integer value (string, float, etc.).

Solution:

// Wrong
res.status('200');
res.status(200.5);

// Correct
res.status(200);

Prevention: Always pass integer literals to res.status().

Error: cookieParser("secret") required for signed cookies

Cause: res.cookie() was called with { signed: true } but no cookie-parser middleware with a secret is registered.

Solution:

const cookieParser = require('cookie-parser');
app.use(cookieParser('my-secret'));

res.cookie('name', 'value', { signed: true });

Warning

Signed cookies require the cookie-parser middleware with a secret string. Without it, Express throws at response time, not at startup — this error only appears when a handler actually tries to set a signed cookie.

Error: Content-Type cannot be set to an Array

Cause: res.set('Content-Type', [...]) was called with an array value, which is not supported for the Content-Type header.

Solution: Pass a single string: res.set('Content-Type', 'application/json').

Error: unknown value for query parser function: ...

Cause: app.set('query parser', value) received an unsupported value.

Solution: Use one of the supported values: 'simple', 'extended', false, or a custom function.

Issue: BigInt values cause cryptic TypeError with res.json() (#7186)

Cause: JSON.stringify() does not support BigInt natively. Passing an object containing BigInt values to res.json() throws a TypeError without a clear message.

Solution: Convert BigInt values to strings or numbers before sending:

res.json({ id: bigintValue.toString() });

Issue: Single-item array query parameter not parsed correctly (#7178)

Cause: When using the default 'simple' query parser, single-element arrays in query strings may not parse as expected, particularly behind certain hosting platforms.

Solution: Switch to the 'extended' query parser: app.set('query parser', 'extended').

Caution

When deploying behind reverse proxies (nginx, cloud load balancers), always configure trust proxy correctly. An incorrect setting causes req.ip, req.protocol, req.hostname, and req.secure to return wrong values, potentially breaking redirects, HTTPS detection, and rate limiting.

Issue: Mounting at '//' does not work as expected (#4557)

Cause: app.use('//', handler) does not mount at the double-slash path as one might expect.

Solution: Avoid double-slash mount paths. Use single-slash paths and handle URL normalization upstream in your reverse proxy.

Debugging

Enable Express debug logs by setting the DEBUG environment variable:

DEBUG=express:* node app.js
express:application booting in development mode +0ms
express:router use / query +2ms
express:router:route new / +0ms

Narrow debug output to specific modules:

Pattern Shows
express:application App initialization and settings
express:router Route matching and dispatch
express:view Template lookup and rendering

7. FAQ

Q: How do I structure a large Express application?

Use the Router to split routes into separate files by feature or resource. Each file exports a router, and the main app mounts them with app.use('/prefix', router). See the examples/route-separation and examples/multi-router patterns included in the Express repository.

Q: How do I handle async/await errors in route handlers?

Express 5 automatically catches rejected promises from async route handlers and forwards the error to your error-handling middleware. You do not need a try/catch wrapper or a separate asyncHandler utility.

Q: Can I use Express with TypeScript?

Yes. Install @types/express for type definitions: npm install --save-dev @types/express. Express itself is written in JavaScript, but TypeScript typings are maintained by the DefinitelyTyped community.

Q: How do I disable the X-Powered-By header?

app.disable('x-powered-by');

Q: What is the difference between res.send() and res.json()?

res.send() auto-detects the body type: strings are sent as HTML, objects/arrays are serialized as JSON (delegating to res.json()), and Buffers are sent as binary. res.json() always serializes as JSON and respects json replacer, json spaces, and json escape settings.

Q: How do I serve an SPA (Single-Page Application) with Express?

Serve static assets with express.static(), then add a catch-all route that sends your index.html:

app.use(express.static('dist'));
app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname, 'dist', 'index.html'));
});

Q: How do I redirect HTTP to HTTPS?

app.set('trust proxy', 1);
app.use((req, res, next) => {
  if (!req.secure) {
    return res.redirect(301, `https://${req.hostname}${req.originalUrl}`);
  }
  next();
});

Q: How do I set cookies that cannot be accessed by client-side JavaScript?

Use the httpOnly option: res.cookie('session', token, { httpOnly: true, secure: true }).

Q: What query parser should I use?

Use 'simple' (default) for flat key=value pairs. Use 'extended' if your frontend sends nested objects (user[name]=tobi) or arrays (colors[]=red&colors[]=blue).

Q: Does app.get() handle settings and routes?

Yes. With one argument, app.get('setting') returns a setting value. With two or more arguments, app.get('/path', handler) defines a GET route. This dual behavior is intentional.

Q: How do I handle CORS?

Use the cors middleware package (npm install cors) or set headers manually:

app.use((req, res, next) => {
  res.set('Access-Control-Allow-Origin', '*');
  res.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  res.set('Access-Control-Allow-Headers', 'Content-Type');
  next();
});