Appearance
Express.js Core Patterns
Core patterns for building Express.js applications: creating apps, configuring settings, composing middleware, mounting route handlers, and structuring server entry points.
When to Use
- Starting a new Express server or API
- Structuring middleware pipelines
- Mounting sub-applications and route handlers
- Configuring app-wide settings like trust proxy or JSON formatting
The Pattern
An Express application is a middleware pipeline. Create the app, configure settings, compose middleware in order, mount route handlers, then start listening.
javascript
import express from "express";
import nocache from "nocache";
import { listen } from "./http.js";
import { cleanupOnExit } from "./process.js";
import securityMiddleware from "./security.js";
import loggingMiddleware from "./logging.js";
import authMiddleware from "./auth.js";
import sessionEndpoint from "./session.js";
import rpcEndpoint from "./rpc.js";
import uiServer from "./ui/index.js";
const server = express();
export default server;
// Settings
server.enable("trust proxy");
server.set("json spaces", 2);
// Global middleware (runs on every request)
server.use(securityMiddleware);
server.use(loggingMiddleware);
server.use(authMiddleware);
// Path-scoped handlers
server.use("/session", sessionEndpoint);
// Multiple paths sharing the same middleware
server.use([
"/budget",
"/goals",
"/home",
"/settings",
], nocache(), signinRequired);
// Sub-applications
server.use("/rpc", nocache(), signinRequired, rpcEndpoint);
server.use(uiServer);
// Start and set up graceful shutdown
const port = config.get("app.port");
const { url, listener } = await listen(server, port);
console.log(`Server running at ${url}`);
cleanupOnExit(async function cleanup() {
if (cleanup.complete) return;
await listener.close();
cleanup.complete = true;
});Minimal setup
The simplest Express server creates an app, attaches a route handler, and listens on a port:
javascript
import express from "express";
const app = express();
app.get("/health", (req, res) => {
res.json({ status: "ok" });
});
app.listen(3000);Key Concepts
Middleware order matters. Middleware runs top-to-bottom. Security and logging come first; route handlers come after authentication.
server.use() is the composition primitive. It accepts:
- A middleware function:
server.use(fn) - A path and middleware:
server.use("/api", fn) - Multiple middleware:
server.use(fn1, fn2, fn3) - An array of paths:
server.use(["/a", "/b"], fn) - Another Express app:
server.use(subApp)
Apply nocache() to multiple paths by passing an array:
javascript
server.use([
"/api",
"/rpc",
], nocache());Chain multiple middleware on a single path to compose authentication, caching, and handler logic:
javascript
server.use(
"/rpc",
nocache(),
signinRequired,
rpcEndpoint,
);Export the app from its module. This lets parent modules mount it with server.use() without knowing its internals.
Separate startup from definition. The app is created and exported in one place; listen() is called in the entry point. This keeps the app testable without starting a server.
Trade-offs
Three common ways to structure an Express application:
Single file — route handlers, middleware, and listen() all in one module. Fine for small scripts and quick prototypes.
Modular middleware — each concern (security, logging, auth) lives in its own file and is imported into a central server module, as in the main example above. Each piece can be tested independently and reordered or swapped without touching the rest of the pipeline.
Sub-applications — distinct areas of the app (an API, a UI server) are created as separate Express instances and mounted with server.use(). This fully isolates each area's routing and middleware from the others.
| Approach | Pros | Cons |
|---|---|---|
| Single file | Simple, easy to read | Doesn't scale |
| Modular middleware | Composable, testable | More files |
| Sub-applications | Self-contained, isolatable | Nesting can obscure flow |
Start modular. Even small servers benefit from separating concerns into middleware modules that can be reordered, removed, or tested independently.