RepoFold

Architecture

This page describes the high-level architecture of Express: the application factory, middleware stack, routing system, and request/response lifecycle. Understanding these components is essential for building applications with Express and for contributing to the framework itself.

System Overview

Express is a lightweight, unopinionated web framework for Node.js built around a middleware-based architecture. The core library resides in the lib directory and consists of six modules that collaborate to handle HTTP requests. The project root (index.js) re-exports the main factory function from lib/express.js.

Rendering diagram…

The diagram shows the repository's major components. The core library (lib) is the foundation, with test providing unit and acceptance tests, and examples demonstrating usage patterns. The project root (index.js) serves as the entry point for consumers.

Core Components

The Express library is composed of six modules in the lib directory, each with a specific responsibility:

ModuleFilePurpose
Applicationlib/application.jsDefines the app object with methods like use, get, post, render, listen
Requestlib/request.jsExtends Node's http.IncomingMessage with Express-specific methods
Responselib/response.jsExtends Node's http.ServerResponse with Express-specific methods
Routerlib/router/index.jsImplements the routing system for matching requests to handlers
Viewlib/view.jsHandles template resolution and rendering
Utilslib/utils.jsProvides helper functions used across the library

The entry point lib/express.js ties these together by creating the application factory and re-exporting prototypes and middleware lib/express.js.

Application Factory

When a developer calls express(), the factory function in lib/express.js creates a new application object. This function:

  1. Creates an instance of the app object defined in lib/application.js
  2. Initializes the internal middleware stack
  3. Sets up the default configuration (e.g., view engine settings, trust proxy)
  4. Returns the app object ready for use

The application object is both a request handler and a factory for creating sub-applications. It can be passed directly to http.createServer() or mounted under another Express application.

Middleware Stack

The middleware stack is the core mechanism by which Express processes HTTP requests. Every request passes through a series of functions (middleware) in the order they were registered.

How the Stack Works

  1. When the application starts, it initializes an empty middleware stack
  2. Each call to app.use() or app.METHOD() (e.g., app.get(), app.post()) adds a layer to the stack
  3. When a request arrives, Express iterates through the stack synchronously
  4. Each middleware function receives the request object, response object, and a next function
  5. A middleware can:
  • End the request-response cycle by sending a response
  • Pass control to the next middleware by calling next()
  • Pass an error to the next error-handling middleware by calling next(err)

Middleware Registration

The app.use() method registers middleware that runs for every request, regardless of path. The app.METHOD() methods register route-specific middleware that only runs when the request method and path match.

// From lib/application.js
app.use = function use(fn) {
 // Adds a middleware layer to the stack
 //...
};

Error-Handling Middleware

Error-handling middleware is defined with four parameters: (err, req, res, next). When any middleware calls next(err), Express skips all regular middleware and looks for the next error-handling middleware in the stack.

Routing System

The routing system matches incoming HTTP requests to registered handlers based on method and path.

Route Registration

Routes are registered using methods like app.get(), app.post(), app.put(), app.delete(), etc. Each call creates a route with:

  • A path pattern (string or regular expression)
  • One or more handler functions
  • Optional route-specific middleware

Route Matching

When a request arrives, Express:

  1. Extracts the HTTP method and URL path from the request
  2. Iterates through the middleware stack
  3. For each layer, checks if the method and path match
  4. If matched, executes the layer's handler functions in sequence
  5. If no match is found, passes to the next layer

The routing system supports:

  • Path parameters: Segments prefixed with : (e.g., /users/:id) are extracted and stored in req.params
  • Regular expressions: Paths can be defined as regex patterns
  • Route arrays: Multiple paths can be handled by a single route
  • Route middleware: Functions that run before the final handler

Route Separation

The examples/route-separation directory demonstrates organizing routes into separate modules. Each module (e.g., site.js, user.js, post.js) exports handler functions, and the main index.js imports them and defines routes using app.get() etc. Route Separation Example.

Request/Response Lifecycle

The complete lifecycle of an HTTP request through Express follows this flow:

Step 1: Request Arrival

An HTTP request arrives at the Node.js server. The app object, which is a valid request handler, receives the request.

Step 2: Middleware Processing

Express begins iterating through the middleware stack. Each middleware function receives (req, res, next). The request object (req) is an instance of Node's http.IncomingMessage extended with Express methods from lib/request.js. The response object (res) is an instance of Node's http.ServerResponse extended with methods from lib/response.js.

Step 3: Route Matching

When a middleware layer corresponds to a route, Express checks if the request method and path match. If they do, the route's handler functions execute in sequence.

Step 4: Response Generation

The handler generates a response using methods like:

  • res.send() - Sends a response of various types
  • res.json() - Sends a JSON response
  • res.render() - Renders a view template and sends the output
  • res.redirect() - Redirects the client to a different URL

Step 5: View Rendering (if applicable)

If res.render() is called, the View system (lib/view.js) locates the template file, compiles it using the configured template engine (e.g., EJS as shown in examples/ejs), and sends the rendered output.

Step 6: Response Sent

Once a response is sent, the request-response cycle ends. No further middleware executes.

Step 7: Error Handling

If any middleware calls next(err) or throws an error synchronously, Express skips remaining regular middleware and looks for error-handling middleware. The error propagates through the stack until caught by an error handler or reaches the default error handler.

Configuration and Settings

Express applications support various configuration settings managed internally by the library. These include:

  • view engine - The default template engine extension
  • views - The directory containing view templates
  • trust proxy - Whether to trust proxy headers for IP addresses
  • jsonp callback name - The query parameter name for JSONP callbacks
  • etag - Whether to generate ETags for responses

Settings are accessed and modified via app.set() and app.get() methods.

Content Negotiation

Express provides built-in content negotiation through utilities in lib/request.js and lib/response.js. The req.accepts() method inspects the Accept header and returns the best matching content type. The res.format() method responds to requests based on the Accept header, selecting the appropriate representation.

Testing Architecture

The repository includes a comprehensive test suite in the test directory. Unit tests cover each core module independently, using a testing framework (likely Mocha) and supertest for HTTP assertions. Acceptance tests in test/acceptance validate the example applications end-to-end.

Generated from commit ae6dd37