Express.js, mapped from the inside out.
Not the tutorial mental model. This is how the actual repository is organized — files in lib/, the runtime composition, the routing stack, and the full request lifecycle, rendered as the system it really is.
§ 01 Express actual library structure (source-level)
This is roughly how the core repo is structured.
express/ # the published package
├── index.js # 11 lines — re-exports lib/express.js
├── lib/
│ ├── express.js # 81 loc — createApplication() factory
│ ├── application.js # 631 loc — the app prototype (public API)
│ ├── request.js # 514 loc — getters spliced above IncomingMessage
│ ├── response.js # 1053 loc — send · json · redirect · sendFile
│ ├── view.js # 205 loc — template resolution
│ └── utils.js # 271 loc — etag, negotiation, param merge
└── package.json # 28 runtime dependencies
router/ # a DEPENDENCY in express 5, not a folder
├── index.js # 748 loc — Router: the stack and handle()
└── lib/
├── layer.js # 247 loc — Layer: compile a path, match()
└── route.js # 242 loc — Route: handlers grouped by verb
Correction worth making early. Almost every Express diagram — including version 4 of this page — shows lib/router/ inside the package. In Express 5 the router was extracted into the standalone router package, and both lib/express.js and lib/application.js reach it through require('router'). Line counts above are measured from express@5.2.1 and router@2.2.0 as published.
§ 02 High-level architecture (how files connect)
The actual composition between the source files. express() is a factory; everything else hangs off the application object it produces.
Express is just a function (app) + a middleware stack (router) + augmented req/res objects
§ 03 Core modules breakdown
Each file in lib/ has a single, well-defined responsibility. Here's what lives where, in actual structural form.
3.1 lib/express.js → Application Factory
createApplication()
├── creates function: app(req, res, next)
├── mixes in:
│ • EventEmitter
│ • application prototype
├── attaches:
│ • app.request prototype
│ • app.response prototype
👉 This is why:
const app = express()
is actually a function you can pass to http.createServer().
3.2 lib/application.js → The real "app"
This is where most APIs live:
Public API surface
- app.use() — add middleware
- app.get() — define routes
- app.post()
- app.listen()
- app.set() / get() — config
- app.handle() — ENTRY POINT
Internal responsibility
- setup req/res prototypes
- attach res.locals
- set headers
- delegate → router.handle()
👉 app.handle() is the true execution entrypoint.
3.3 lib/request.js → req extension
Extends Node's:
http.IncomingMessage
Adds:
req ├── req.params ├── req.query ├── req.body ├── req.get() ├── req.accepts()
3.4 lib/response.js → res extension
Extends:
http.ServerResponse
Adds:
res ├── res.send() ├── res.json() ├── res.status() ├── res.redirect() ├── res.render()
3.5 Router system (the router package)
This is the REAL core of Express — and in version 5 it is not even inside the package. Router, Route and Layer live in router@2.x, which express depends on; path-to-regexp is in turn a dependency of that. Three files, 1237 lines, and every routing behaviour on this page.
§ 04 Router internals (the real engine)
Router ├── stack: Layer[] │ ├── use() → adds middleware Layer ├── METHOD() → creates Route → wraps in Layer └── handle() → iterates stack
4.1 Layer (smallest unit)
Layer ├── path ├── match(path) ├── handle(req, res, next)
👉 Think: "One executable step in the pipeline"
4.2 Route (groups handlers per HTTP method)
Route (/users) ├── stack: Layer[] ├── get() ├── post() └── dispatch()
4.3 Router (orchestrator)
Router
├── stack: [
│ Layer("/", middleware),
│ Layer("/users", Route),
│ ]
Execution:
for each layer in stack:
if match:
execute handler
→ next()
👉 This is literally a linked middleware pipeline.
§ 05 FULL request lifecycle (accurate flow)
Step through the lifecycle. Each click advances one stage along the real path a request takes from Node's HTTP server to res.send().
§ 06 Middleware system (how it fits structurally)
Middleware is not a "separate thing".
It is literally:
Layer {
handle: function(req, res, next)
}
Types:
app-level
- runs for every request
router-level
- scoped to a sub-router
route-level
- per HTTP method
error-handling
- 4 args → treated as error handler
built-in
- express.static()
- express.json()
👉 All of them = just Layers in a stack.
§ 07 Putting EVERYTHING together (true mental model)
Final compressed model (lock this in)
express() → app (function) app → delegates to router router → stack of layers layer → executes middleware or route route → executes method handlers
Express is NOT a framework with controllers, not MVC, not magic routing.
It is a programmable pipeline of functions over HTTP, implemented as a stack of Layer objects.
§ 08 Routing internals — path-to-regexp, params, matching
This is where Express stops being "simple" and becomes clever string → regex machinery.
8.1 How paths actually work
When you write:
app.get("/users/:id", handler)
Express does NOT store /users/:id as a string.
👉 It compiles it using path-to-regexp into something like:
^\/users\/([^\/]+?)\/?$
And stores metadata:
{
keys: [{ name: "id" }],
regexp: /^\/users\/([^\/]+?)\/?$/
}
8.2 Matching flow inside a Layer
Layer.match(path) ↓ regexp.test(path) ↓ if match: extract params
8.3 Param extraction
When request comes:
GET /users/42
Regex match produces:
["/users/42", "42"]
Mapping happens:
req.params = {
id: "42"
}
Express routing = Regex engine + param extraction layer. NOT string matching.
8.4 Order matters more than you think
app.get("/users/:id", handlerA)
app.get("/users/profile", handlerB)
👉 /users/profile will hit handlerA unless ordered correctly.
Why?
/users/profile → matches /users/:id
8.5 Nested routers (composition model)
app.use("/api", router)
Internally:
Layer("/api")
↓
strips prefix → "/users"
↓
passes to child router
👉 Routers are stackable pipelines.
§ 09 Performance — why Express is "slower" than Fastify
Let's break this down at a systems level.
9.1 Express execution cost
Each request goes through:
for every Layer: check match call function manage next()
Overhead sources:
- Function calls per middleware
- Closure allocations
- Dynamic typing
- Regex matching per layer
- No compilation step
9.2 Fastify's advantage
Fastify builds a precompiled routing tree.
Instead of:
O(n) → scan all layers
It does:
O(log n) → lookup in route tree
9.3 Visual comparison
// Express — linear scan
cost grows linearly with stack depth
// Fastify — tree lookup
cost grows logarithmically · O(log n)
9.4 Serialization cost
Express:
res.json(data) → JSON.stringify()
Fastify:
- Uses compiled serializers (schema-based)
- Avoids runtime cost
9.5 Benchmarks reality
- Express: flexible, dynamic
- Fastify: structured, optimized
👉 Difference matters at:
10k–100k req/sec scale
NOT typical apps.
Express trades performance for flexibility and simplicity.
Fastify trades flexibility for speed.
§ 10 Production architecture — avoiding middleware chaos
This is where most Express apps fail.
10.1 The common mistake
app.use(auth) app.use(validate) app.use(log) app.use(rateLimit) app.use(...)
👉 Becomes:
UNCONTROLLED GLOBAL PIPELINE
10.2 Proper layered architecture
You want bounded pipelines per route group:
app ├── /auth │ ├── validation │ └── controller │ └── /users ├── auth ├── validation └── controller
10.3 Clean folder structure
src/ ├── routes/ │ ├── user.routes.js │ └── auth.routes.js │ ├── controllers/ ├── services/ ├── middleware/ └── db/
10.4 Route-level composition (correct way)
router.post(
"/claim",
validateInput,
rateLimit,
async (req, res) => {
const voucher = await voucherService.create(req.body);
res.json(voucher);
}
);
10.5 Service layer separation
HTTP concerns
- parse req · format res
- status codes · errors
Business logic
- orchestrate workflow
- domain rules
Persistence
- queries · transactions
- data shape
10.6 Example (your voucher system)
10.7 Error handling (critical)
app.use((err, req, res, next) => {
res.status(500).json({ error: err.message });
});
Internally:
Layer with 4 args → treated as error handler
Middleware should be:
- Scoped (not global)
- Ordered intentionally
- Minimal per request
§ 11 FINAL — Unified deep mental model
The real "senior-level" understanding (lock this in)
Express is:
- A request dispatcher (router)
- A function pipeline (middleware)
- A thin HTTP abstraction (req/res)
Everything else in your backend is your architecture on top of it.
§ 12 Every breakdown on this page
Each section, subsection and diagram on this page opens a breakdown: prose plus interactive figures that run a real routing engine rather than describing one.