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.
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:
| Module | File | Purpose |
|---|---|---|
| Application | lib/application.js | Defines the app object with methods like use, get, post, render, listen |
| Request | lib/request.js | Extends Node's http.IncomingMessage with Express-specific methods |
| Response | lib/response.js | Extends Node's http.ServerResponse with Express-specific methods |
| Router | lib/router/index.js | Implements the routing system for matching requests to handlers |
| View | lib/view.js | Handles template resolution and rendering |
| Utils | lib/utils.js | Provides 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:
- Creates an instance of the
appobject defined inlib/application.js - Initializes the internal middleware stack
- Sets up the default configuration (e.g., view engine settings, trust proxy)
- 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
- When the application starts, it initializes an empty middleware stack
- Each call to
app.use()orapp.METHOD()(e.g.,app.get(),app.post()) adds a layer to the stack - When a request arrives, Express iterates through the stack synchronously
- Each middleware function receives the request object, response object, and a
nextfunction - 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:
- Extracts the HTTP method and URL path from the request
- Iterates through the middleware stack
- For each layer, checks if the method and path match
- If matched, executes the layer's handler functions in sequence
- 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 inreq.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 typesres.json()- Sends a JSON responseres.render()- Renders a view template and sends the outputres.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 extensionviews- The directory containing view templatestrust proxy- Whether to trust proxy headers for IP addressesjsonp callback name- The query parameter name for JSONP callbacksetag- 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.