Install

expressjs/express

Express.js Framework

Last updated on Dec 09, 2025 (Commit: 9eb7001)

Overview

Relevant Files
  • index.js
  • package.json
  • lib/express.js
  • lib/application.js
  • Readme.md

Express is a fast, unopinionated, minimalist web framework for Node.js that provides a robust foundation for building HTTP servers and APIs. It abstracts away the complexity of Node's native HTTP module while maintaining flexibility and performance.

Core Architecture

Express follows a modular, middleware-based architecture. The framework is initialized through a factory function that creates an application instance, which acts as both a request handler and an event emitter. The application instance inherits methods from the Express prototype and the Node.js EventEmitter, allowing it to handle HTTP requests and emit lifecycle events.

import express from 'express'

const app = express()

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

app.listen(3000)

Key Components

The framework consists of several core modules working together:

  1. Application (lib/application.js) - Manages configuration, settings, middleware stack, and route handling. Initializes default middleware and provides methods for routing and rendering.

  2. Router (router package) - Handles route matching and dispatching requests to appropriate handlers. Supports HTTP methods (GET, POST, PUT, DELETE, etc.) and middleware chaining.

  3. Request/Response (lib/request.js, lib/response.js) - Extends Node's native request and response objects with convenience methods for headers, cookies, content negotiation, and more.

  4. View Engine (lib/view.js) - Supports 14+ template engines for server-side rendering with configurable view directories and caching.

Middleware System

Express uses a middleware-based request processing pipeline. Middleware functions receive (req, res, next) and can modify requests, send responses, or pass control to the next middleware. This enables composable, reusable request handling logic.

app.use(express.json())
app.use(express.static('public'))
app.use((req, res, next) => {
  console.log('Request received')
  next()
})

Built-in Features

  • Robust routing with support for parameters, wildcards, and regex patterns
  • Content negotiation for handling multiple response formats
  • HTTP helpers for redirects, caching, and status codes
  • Cookie and session management through middleware
  • Static file serving with express.static()
  • Error handling with centralized error middleware
  • View rendering with multiple template engine support

Dependencies

Express depends on specialized packages for core functionality: body-parser for request parsing, router for routing logic, send for file serving, finalhandler for error handling, and various utility packages for HTTP operations like accepts, content-type, and etag.

Architecture & Core Components

Relevant Files
  • lib/express.js
  • lib/application.js
  • lib/request.js
  • lib/response.js
  • lib/view.js
  • lib/utils.js

Express is built on a modular architecture that extends Node.js's native HTTP server with a powerful middleware and routing system. The framework uses prototype-based inheritance to augment request and response objects with Express-specific functionality.

Core Application Factory

The entry point is lib/express.js, which exports a createApplication() function. This factory creates an Express app by:

  1. Creating a middleware function that acts as the main request handler
  2. Mixing in EventEmitter prototype for event capabilities
  3. Mixing in application methods from lib/application.js
  4. Creating custom request and response prototypes that inherit from Node.js's IncomingMessage and ServerResponse
function createApplication() {
  var app = function(req, res, next) {
    app.handle(req, res, next);
  };
  mixin(app, EventEmitter.prototype, false);
  mixin(app, proto, false);
  app.request = Object.create(req, { app: { value: app } });
  app.response = Object.create(res, { app: { value: app } });
  app.init();
  return app;
}

Application Lifecycle

The Application prototype (lib/application.js) manages the app's configuration and request handling:

  • Initialization (app.init()): Sets up caches, engines, settings, and lazily initializes the router
  • Request Handling (app.handle()): Sets up circular references between req/res, applies Express prototypes, and delegates to the router
  • Middleware (app.use()): Registers middleware functions or mounts sub-applications at specific paths
  • Routing (app.route(), HTTP verbs): Creates isolated middleware stacks for specific paths and HTTP methods

Request & Response Extensions

Request (lib/request.js) extends http.IncomingMessage with methods for:

  • Content negotiation: accepts(), acceptsCharsets(), acceptsEncodings(), acceptsLanguages()
  • Header access: get(), header()
  • Request properties: ip, ips, hostname, protocol, secure, fresh, stale, xhr
  • Parsing: is(), range()

Response (lib/response.js) extends http.ServerResponse with methods for:

  • Status & headers: status(), set(), get(), type()
  • Content: send(), json(), jsonp(), render()
  • Redirects: redirect(), location()
  • Cookies: cookie(), clearCookie()
  • Streaming: download(), sendFile()

Middleware & Router Integration

Express uses the router package for route matching and middleware execution. The router maintains a stack of middleware functions that are executed in order. When a request arrives:

  1. The app's handle() method sets up prototypes and locals
  2. The router processes middleware and routes in sequence
  3. Matched routes execute their handler functions
  4. Errors bubble up to error-handling middleware

View Rendering

The View class (lib/view.js) handles template rendering:

  • Resolves template files from configured view directories
  • Loads and caches template engines (EJS, Pug, Handlebars, etc.)
  • Normalizes sync/async engine callbacks using process.nextTick()

Configuration & Settings

The application maintains settings for:

  • etag: ETag generation strategy (weak/strong)
  • query parser: Query string parsing method
  • trust proxy: Proxy trust configuration for IP detection
  • view engine: Default template engine
  • views: Directory path for template files

Settings are inherited by mounted sub-applications through prototype chains, enabling hierarchical configuration.

Loading diagram...

Routing System

Relevant Files
  • lib/application.js
  • lib/express.js
  • test/Router.js
  • test/Route.js
  • examples/route-middleware/index.js

Express uses a two-tier routing architecture: the Router handles request dispatching and middleware chaining, while Route objects manage HTTP method-specific handlers for individual paths. The Router is lazily instantiated on first access and configured with case sensitivity and strict routing options.

Router Architecture

The Router is the core request dispatcher. It maintains a stack of middleware and routes, matching incoming requests against registered paths and HTTP methods. When a request arrives, the Router iterates through its stack, executing matching middleware and routes in order. It supports nested routers through router.use(), enabling modular application structure.

Key Router methods:

  • router.use(path, handler) - Register middleware for all HTTP methods
  • router.get(), router.post(), etc. - Register handlers for specific HTTP verbs
  • router.route(path) - Create or retrieve a Route object for a path
  • router.param(name, callback) - Register parameter handlers
  • router.handle(req, res, done) - Dispatch a request through the middleware stack

Route Objects

A Route represents a specific path with isolated middleware stacks for each HTTP method. Routes are created via app.route(path) or router.route(path). Each Route maintains separate handler arrays for GET, POST, PUT, DELETE, and other methods, plus an .all() stack that runs for any HTTP method.

app.route('/users/:id')
  .get(function(req, res) { /* handle GET */ })
  .post(function(req, res) { /* handle POST */ })
  .put(function(req, res) { /* handle PUT */ })

Middleware Chaining

Routes support multiple middleware handlers that execute sequentially. Each handler receives (req, res, next) and must call next() to proceed. Error handlers receive (err, req, res, next) and are invoked when errors occur.

app.get('/user/:id', loadUser, checkAuth, function(req, res) {
  res.send('User: ' + req.user.name);
});

Parameter Handling

Route parameters (e.g., :id) are extracted and stored in req.params. Parameter callbacks registered via app.param(name, callback) execute before route handlers, enabling validation or data loading:

app.param('id', function(req, res, next, id) {
  req.user = loadUserFromDB(id);
  next();
});

Configuration Options

The Router accepts options during initialization:

  • caseSensitive - Treat /Users and /users as different routes (default: false)
  • strict - Treat /users and /users/ as different routes (default: false)

These are controlled via app.set('case sensitive routing', true) and app.set('strict routing', true).

Error Handling

Errors thrown in handlers or passed to next(err) skip regular middleware and route to error handlers (4-parameter functions). Error handlers execute in order until one handles the error or passes it to the next handler.

app.use(function(err, req, res, next) {
  res.status(500).send('Error: ' + err.message);
});

Nested Routers

Routers can be mounted at paths, creating modular route hierarchies:

var apiRouter = express.Router();
apiRouter.get('/users', function(req, res) { /* ... */ });
app.use('/api', apiRouter);  // Routes available at /api/users

Middleware & Request Pipeline

Relevant Files
  • lib/application.js
  • lib/express.js
  • test/middleware.basic.js
  • test/app.use.js
  • examples/route-middleware/index.js

Request Pipeline Overview

When an HTTP request arrives, Express processes it through a carefully orchestrated pipeline. The application instance itself is a function that delegates to app.handle(), which sets up the request context and passes control to the router. The router then executes middleware and routes in registration order, with each middleware able to modify the request, response, or pass control to the next handler.

Loading diagram...

Middleware Registration with app.use()

The app.use() method registers middleware globally or for specific paths. It accepts flexible arguments: a path (optional), middleware functions, or arrays of middleware. Middleware registered without a path defaults to '/' and matches all requests.

// Global middleware
app.use(function(req, res, next) {
  console.log('Request:', req.method, req.url);
  next();
});

// Path-specific middleware
app.use('/api', function(req, res, next) {
  res.setHeader('X-API', 'true');
  next();
});

// Multiple middleware
app.use(fn1, fn2, fn3);

// Array of middleware
app.use([fn1, fn2], fn3);

Middleware Execution Flow

Each middleware function receives (req, res, next) and must call next() to proceed to the next handler. If next() is not called, the pipeline stops and the response must be sent. Middleware executes in registration order, allowing early middleware to modify the request before later handlers see it.

app.use(function auth(req, res, next) {
  req.user = loadUser();
  next();
});

app.use(function logging(req, res, next) {
  console.log('User:', req.user.name);
  next();
});

app.get('/', function(req, res) {
  res.send('Hello ' + req.user.name);
});

Route-Level Middleware

Routes can have their own middleware stack that executes before the route handler. This enables per-route validation, authentication, or data loading without affecting other routes.

app.get('/user/:id', loadUser, checkAuth, function(req, res) {
  res.send('User: ' + req.user.name);
});

In this example, loadUser and checkAuth execute before the final handler, each calling next() to proceed.

Error Handling Middleware

Error handlers are middleware with four parameters: (err, req, res, next). When an error is passed to next(err) or thrown in a handler, Express skips regular middleware and routes directly to error handlers.

app.use(function(req, res, next) {
  next(new Error('Something went wrong'));
});

app.use(function(err, req, res, next) {
  res.status(500).send('Error: ' + err.message);
});

Mounted Applications

Express apps can be mounted as middleware on other apps using app.use(path, app). The mounted app receives requests matching its mount path, with req.url adjusted to remove the mount prefix. This enables modular application structure.

var blog = express();
var api = express();

blog.get('/', function(req, res) {
  res.send('Blog home');
});

api.get('/posts', function(req, res) {
  res.json([]);
});

app.use('/blog', blog);
app.use('/api', api);

Request Context Setup

The app.handle() method prepares each request by setting up prototypes and locals. It establishes circular references between req and res, applies Express-specific prototypes to both objects, and initializes res.locals for template data. This ensures all middleware and routes have access to Express methods and properties.

app.handle = function handle(req, res, callback) {
  req.res = res;
  res.req = req;
  Object.setPrototypeOf(req, this.request);
  Object.setPrototypeOf(res, this.response);
  if (!res.locals) {
    res.locals = Object.create(null);
  }
  this.router.handle(req, res, done);
};

Finalhandler

If no middleware sends a response, the finalhandler package provides a default response. It logs errors in development mode and sends appropriate HTTP status codes (404 for no match, 500 for errors). This ensures every request receives a response, preventing hanging connections.

Request & Response Objects

Relevant Files
  • lib/request.js
  • lib/response.js
  • lib/utils.js

Express extends Node.js's native IncomingMessage and ServerResponse objects with convenience methods for handling HTTP requests and responses. These enhanced objects provide a developer-friendly API for content negotiation, header manipulation, cookie handling, and response formatting.

Request Object

The request object (req) extends Node's IncomingMessage with methods for inspecting incoming data and metadata.

Content Negotiation:

req.accepts('json', 'html')        // Returns best match based on Accept header
req.acceptsEncodings('gzip', 'deflate')
req.acceptsCharsets('utf-8', 'iso-8859-1')
req.acceptsLanguages('en', 'es')

Request Properties:

req.protocol          // 'http' or 'https' (respects X-Forwarded-Proto)
req.secure            // Boolean shorthand for protocol === 'https'
req.ip                // Client IP (respects trust proxy setting)
req.ips               // Array of IPs from X-Forwarded-For
req.hostname          // Host without port
req.host              // Host with port
req.path              // URL pathname
req.query             // Parsed query string object
req.subdomains        // Array of subdomains

Header & Type Inspection:

req.get('Content-Type')    // Get header (case-insensitive)
req.is('json')             // Check if request is JSON
req.fresh                  // Check if response is still fresh (ETag/Last-Modified)
req.stale                  // Inverse of fresh
req.xhr                    // Check if X-Requested-With: XMLHttpRequest

Range Requests:

req.range(1000)  // Parse Range header for byte ranges (useful for video streaming)

Response Object

The response object (res) extends Node's ServerResponse with methods for sending data and managing headers.

Status & Headers:

res.status(200)                    // Set HTTP status code
res.set('Content-Type', 'json')    // Set header (or pass object)
res.get('Content-Type')            // Get header value
res.append('Set-Cookie', 'foo=bar') // Append to header
res.type('json')                   // Set Content-Type via MIME type

Sending Responses:

res.send('Hello')                  // Send string/buffer/object
res.json({ user: 'tj' })           // Send JSON with proper headers
res.jsonp({ user: 'tj' })          // Send JSONP with callback support
res.sendStatus(204)                // Send status with standard message
res.sendFile('/path/to/file')      // Stream file to client
res.download('/path/to/file')      // Send file as attachment

Content Formatting:

res.format({
  'text/plain': () => res.send('text'),
  'text/html': () => res.send('<p>html</p>'),
  'application/json': () => res.json({ message: 'json' })
})

Cookies & Redirects:

res.cookie('name', 'value', { maxAge: 900000, httpOnly: true })
res.clearCookie('name')
res.location('/foo/bar')
res.redirect(301, 'http://example.com')

Rendering & Links:

res.render('view', { title: 'Page' })  // Render template
res.links({ next: '/page/2', last: '/page/5' })  // Set Link header
res.vary('Accept')                     // Add to Vary header
res.attachment('filename.pdf')         // Set Content-Disposition

Utility Functions

The lib/utils.js module provides helper functions for type normalization and ETag generation:

normalizeType('json')              // Returns { value: 'application/json', params: {} }
normalizeTypes(['json', 'html'])   // Normalize array of types
setCharset('text/html', 'utf-8')   // Add charset to Content-Type

Method Chaining

Most response methods return this, enabling fluent API patterns:

res.status(200)
   .set('Content-Type', 'application/json')
   .json({ success: true })

Key Design Patterns

  1. Content Negotiation - Automatically select response format based on Accept headers
  2. Proxy Trust - Respect X-Forwarded-* headers when behind reverse proxies
  3. ETag Support - Automatic ETag generation for cache validation
  4. Streaming - Efficient file serving via sendFile() and download()
  5. Error Handling - Status code validation with clear error messages

View Rendering & Template Engines

Relevant Files
  • lib/view.js
  • lib/application.js
  • lib/response.js
  • examples/ejs/index.js
  • examples/markdown/index.js

Express provides a flexible view rendering system that decouples template engines from the core framework. Views are resolved from a configurable directory, rendered with data, and sent as HTTP responses.

View Resolution & Configuration

The View class in lib/view.js handles template file discovery and engine loading. When rendering a view, Express:

  1. Resolves the view name to a file path using the configured views directory
  2. Determines the template engine based on file extension or default engine setting
  3. Loads the engine module (cached for performance)
  4. Executes the engine with the template path and data

Key configuration methods:

app.set('views', './views');           // View directory (default: CWD/views)
app.set('view engine', 'ejs');         // Default engine extension
app.engine('html', require('ejs').__express);  // Map extension to engine

Rendering Pipeline

When res.render(view, options, callback) is called:

  1. Merge locals: Combines app.locals, res.locals, and render options
  2. Cache check: Returns cached view if caching is enabled (production by default)
  3. View instantiation: Creates a View object that resolves the file path
  4. Engine execution: Calls the template engine with path, merged options, and callback
  5. Response: Sends rendered HTML with 200 status and text/html content type
// In route handler
res.render('users', { users: data }, function(err, html) {
  if (err) return next(err);
  res.send(html);
});

// Or without callback (auto-sends response)
res.render('users', { users: data });

Template Engine Integration

Template engines must export an __express function matching the signature: (path, options, callback). Express provides two integration patterns:

Built-in engines (EJS, Pug, etc.) export __express directly:

app.engine('ejs', require('ejs').__express);

Custom engines can be registered with any function:

app.engine('md', function(path, options, fn) {
  fs.readFile(path, 'utf8', function(err, str) {
    if (err) return fn(err);
    var html = marked.parse(str);
    fn(null, html);
  });
});

View Locals & Data Flow

Data flows to templates through multiple layers:

Loading diagram...

Use res.locals in middleware to expose data to all subsequent routes:

app.use(function(req, res, next) {
  res.locals.user = req.user;
  res.locals.session = req.session;
  next();
});

View Lookup & File Resolution

The View.prototype.lookup() method searches for templates in configured root directories. It supports two file patterns:

  • <name>.<ext> - Direct file match
  • <name>/index.<ext> - Directory index pattern

This enables organizing views hierarchically while maintaining clean render calls.

Built-in Middleware & Utilities

Relevant Files
  • lib/express.js
  • lib/utils.js
  • test/express.json.js
  • test/express.static.js
  • test/express.urlencoded.js
  • test/express.raw.js
  • test/express.text.js

Express provides built-in middleware and utility functions for common request handling tasks. These are exposed directly on the main express object and leverage external packages like body-parser and serve-static for robust, production-ready implementations.

Body Parsing Middleware

Express includes four body parsing middleware functions for handling different content types:

express.json(options) - Parses incoming request bodies with application/json content type. Populates req.body with the parsed JSON object.

express.urlencoded(options) - Parses URL-encoded form data (application/x-www-form-urlencoded). Supports both simple and extended syntax for nested objects and arrays.

express.text(options) - Parses plain text bodies (text/plain). Stores the text as a string in req.body.

express.raw(options) - Parses binary data (application/octet-stream). Stores the raw bytes as a Buffer in req.body.

All body parsers share common options:

  • limit - Maximum request body size (default: 100kb). Accepts strings like '1mb' or byte numbers.
  • type - Content-Type to parse. Can be a string, array, or function for custom matching.
  • verify - Function (req, res, buf, encoding) called before parsing for custom validation or logging.
  • inflate - Whether to handle compressed bodies (gzip, deflate). Default: true.
  • strict - For JSON only; if true, only objects and arrays are parsed (default: true).
  • extended - For URL-encoded only; if true, uses qs library for complex nested structures (default: true).

Static File Serving

express.static(root, options) - Serves static files from a directory. Requires a root path string.

Key options include:

  • maxAge - Cache-Control max-age in milliseconds or string like '1d' (default: 0).
  • cacheControl - Enable/disable Cache-Control header (default: true).
  • lastModified - Include Last-Modified header (default: true).
  • etag - Enable/disable ETag generation (default: true).
  • acceptRanges - Enable HTTP range requests (default: true).
  • dotfiles - How to handle dotfiles: 'allow', 'deny', or 'ignore' (default: 'ignore').
  • index - Index file(s) to serve for directories (default: ['index.html']).
  • redirect - Redirect to trailing slash for directories (default: true).
  • fallthrough - Pass to next middleware on 404 (default: true).
  • setHeaders - Function to customize response headers.

Utility Functions

The lib/utils.js module provides internal utilities for content negotiation and configuration:

  • normalizeType(type) - Converts shorthand types ('html') to MIME types ('text/html').
  • normalizeTypes(types) - Batch version for arrays of types.
  • compileETag(val) - Converts ETag configuration to a generator function.
  • compileQueryParser(val) - Converts query parser config to a parsing function.
  • compileTrust(val) - Converts proxy trust config to a validation function.
  • setCharset(type, charset) - Adds charset parameter to Content-Type headers.

These utilities support flexible configuration: boolean values, strings, numbers, arrays, or custom functions.

Error Handling

Body parsers return 4xx errors for invalid input:

  • 400 - Malformed JSON, invalid encoding, or parse errors.
  • 413 - Request entity too large (exceeds limit).
  • 415 - Unsupported content type or encoding.

Errors include a type property (e.g., 'entity.parse.failed') and can be caught by error middleware.

Integration Example

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

app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
app.use(express.static('public', { maxAge: '1h' }));

app.post('/api/data', (req, res) => {
  res.json(req.body);
});

app.listen(3000);

Common Patterns & Examples

Relevant Files
  • examples/hello-world/index.js
  • examples/multi-router/index.js
  • examples/mvc/index.js
  • examples/error-pages/index.js
  • examples/route-middleware/index.js
  • examples/params/index.js
  • examples/cookies/index.js

Express provides a rich collection of examples demonstrating core patterns and best practices. These examples serve as practical starting points for building web applications with different architectural approaches.

Getting Started: Hello World

The simplest Express application requires just a few lines of code:

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

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

app.listen(3000);

This pattern establishes the foundation: create an app instance, define routes, and start the server. The res.send() method automatically sets content-type headers and sends the response.

Organizing Routes with Multiple Routers

As applications grow, separating routes into logical modules becomes essential. The multi-router pattern uses app.use() to mount routers at specific paths:

app.use('/api/v1', require('./controllers/api_v1'));
app.use('/api/v2', require('./controllers/api_v2'));

Each controller module exports a router instance, enabling clean separation of concerns and versioning strategies.

Route-Level Middleware and Authorization

Middleware can be applied to individual routes to handle authentication, validation, and authorization:

app.get('/user/:id/edit', loadUser, andRestrictToSelf, function(req, res) {
  res.send('Editing user ' + req.user.name);
});

Middleware functions execute in order, allowing you to load resources, check permissions, and pass data via req properties before reaching the final handler.

Parameter Handling and Validation

The app.param() method intercepts route parameters for preprocessing and validation:

app.param('user', function(req, res, next, id) {
  req.user = users[id];
  if (req.user) next();
  else next(createError(404, 'User not found'));
});

This pattern centralizes parameter logic, reducing duplication across multiple routes.

Error Handling and Custom Error Pages

Express supports error-handling middleware with a 4-parameter signature (err, req, res, next):

app.use(function(err, req, res, next) {
  res.status(err.status || 500);
  res.render('500', { error: err });
});

Error handlers must be registered after all other middleware and routes. Content negotiation with res.format() allows serving different error representations (HTML, JSON, plain text).

Session Management and View Locals

The MVC example demonstrates session setup and exposing data to templates:

app.use(session({ secret: 'key', resave: false }));
app.use(function(req, res, next) {
  res.locals.messages = req.session.messages || [];
  next();
});

This pattern ensures view templates have access to session data without explicit passing.

Cookies are parsed and managed through middleware:

app.use(cookieParser('secret'));
app.post('/', function(req, res) {
  res.cookie('remember', 1, { maxAge: 60000 });
  res.redirect('/');
});

The cookieParser middleware populates req.cookies and req.signedCookies, while res.cookie() and res.clearCookie() manage cookie lifecycle.