00 Field guide · Express 5 on Node.js on Linux

Watch one request travel from the wire, through Express, to your handler — and back.

Express is a few thousand lines of plain JavaScript standing between your handlers and node:http — a fifth layer on a stack that now runs five deep. Below the two real boundaries — the C++ gate and the syscall gate — nothing has changed; above them, two new seams appear, and both are ordinary function calls: node:http calling the app (because the app is a 'request' listener), and Express's Layer walk calling you. Pick a journey and press play: one request threads the whole stack, kernel to kernel, and every Express step happens on the same one thread that runs everything else. The framework is one dispatch loop — everything it does is middleware, and next() is the loop's own continuation, lent to your code.

Journey · GET, end to end through Express
00 / 00
FRAME 00 · —

Pick a journey and press play

Each frame moves the work-token to the part doing the work and lights everything it has passed. Chartreuse is your code, graphite is Express, green is Node's JavaScript API, purple is the native core of C and C++, and teal is the operating system. The amber gates are the only two real crossings; the two graphite seams above them are ordinary function calls. The journey chips below are coloured by what carries the work: graphite journeys are dispatched through Express's Layer walk, green ones ride the event loop alone, purple ones take a thread-pool thread, teal ones buy real parallelism with another process or core, and plum ones are runtime machinery — modules, V8, memory.

Journeys
Your code Express · the Layer stack Node's JS API · lib/*.js Native core · V8 · libuv · C++ Operating system Gate (C++ / syscall) Seam (a function call) click any node → full detail · press F to leave fit view and read the rest

01 The mental model

Five layers, two seams — and the two gates that actually matter.

Almost every confusing thing about this stack follows from one arrangement. Your handlers, Express, and Node's own JavaScript all run on a single thread, in a single V8 isolate, with no ability to touch a file or a socket. Express is not infrastructure — it is a dependency in your node_modules, promoted to a band of its own here because every request passes through its one Layer stack on the way to you. Beneath the JavaScript sits the native core — V8 itself, libuv, and a handful of vendored C libraries — and beneath that, the kernel.

The crossings are not all the same kind of thing, and the map refuses to pretend they are. The two seams at the top — node:http calling the app, and Express's walk calling your handler — are ordinary function calls on one shared stack: free to cross, drawn as hairlines. The two gates below are real boundaries: JavaScript reaches C++ only through internalBinding, and C++ reaches the machine only through syscalls. Both gates cost something to cross, and both are where an operation stops being yours and starts being somebody else's problem for a while.

The part people get wrong is what happens after a crossing. A socket operation stays on the event loop, because a socket can be watched — the kernel will tell you the instant it becomes readable. A file operation cannot be watched, because a file is always ready, so it is handed to a pool of four real threads instead. Same API shape, completely different machinery.

the recurring shape a request  →  'request' emit → the app → the Layer walk → your handler  →  res.send  →  node:http  →  internalBinding → a C++ wrapper  →  the kernel writes to the wire. One thread the whole way down the JavaScript; two gates below it.

02 Your code · unprivileged · one thread

What you wrote — and what it can't do on its own.

Your modules, your dependencies, the module system that loaded them and the handful of globals available without importing anything. None of it can read a file, open a socket or set a timer by itself; it describes what it wants and hands the description to Node. Click a part to open it, or press its play button to watch its journey on the map above.

03 The Express layer · plain JavaScript · one Layer stack

A framework that is one data structure and one loop.

Everything Express does is built from a single mechanism. Registration — app.use, app.get, a mounted router — pushes a Layer onto one array. A request is a walk down that array: test each Layer’s compiled path pattern, call the handler on a match, and hand it next — a closure over the walk’s own index, so nothing advances until your code says so. Routes, static files, body parsing, the 404 page and error handling are all this one walk wearing different functions.

And all of it is plain JavaScript in your process, on the same single thread as everything else in the two bands above and below. Express opens no sockets, holds no descriptors, and spends no time in the kernel — the four cards below are the whole anatomy, and the eight after them are the surface you actually touch.

The surface. Where the anatomy above shows up in the API you call every day:

04 The two seams · function calls, not boundaries

Where Node calls Express, and where Express calls you.

The amber gates further down are real crossings — privilege changes, different languages, work changing hands. These two are not. The ‘request’ seam is node:http’s emit loop calling the app, because app.listen registered the app as an ordinary 'request' listener. The dispatch seam is Express’s walk calling your handler — inversion of control on a single shared call stack, which is exactly why a synchronous throw in your handler is catchable by the dispatcher. Same thread, same stack, no queue between: drawn as hairlines, because that is what they cost.

05 Node's JavaScript API · lib/*.js

Still JavaScript. Still your thread.

This is the surface you actually program against, and it is worth remembering that it is written in JavaScript and runs on the same single thread your handlers do. fs.readFile is a JavaScript function that packages up a request; http is a JavaScript state machine over a socket. The work leaves your thread only after the next gate — and for some of these families, it never leaves at all.

06 The first gate · JavaScript ⇄ C++

Where Node stops being JavaScript.

Every asynchronous API in Node bottoms out in a call across this boundary. On the far side, a C++ object is created that owns a libuv structure and holds your callback alive until the operation completes. That ownership is worth remembering: a leaked handle leaks your callback, and everything your callback closes over.

07 The native core · C and C++

The engine, the loop, and the four threads.

V8 executes your JavaScript and owns the heap it lives in. libuv provides the event loop, the platform abstraction over epoll, kqueue and IOCP, and a thread pool of four threads that can run C but not JavaScript. Around them sit the vendored libraries that do the specialist work: parsing HTTP, doing cryptography, resolving names, compressing bytes.

08 The second gate · the only way into the kernel

A few dozen syscalls are the whole of Node's I/O.

Underneath every stream, promise and callback is a small vocabulary of kernel calls — open, read, write, socket, bind, listen, accept, epoll_wait. Running strace against a Node process is a genuinely clarifying experience: the abstraction tower is tall, and what comes out of the bottom is short.

09 The operating system · kernel and machine

Where the bytes finally live and move.

The cores your threads are placed on, the RAM your heap and your Buffers occupy, the disk with its page cache in front of it, and the network stack that completed the TCP handshake before your process knew a client existed. Every layer above exists to share these four safely.

10 The module index · every core API you actually reach for

The whole surface, grouped by what you're trying to do.

Node ships around forty built-in modules. These are the ones that appear in real applications, grouped by the job rather than alphabetically. Each opens with what it does, the parts of it that surprise people, and where its work physically lands on the map above.

One grouping is worth defending. Concurrency is dealing with many things at once; parallelism is doing many things at once. Node hands you enormous concurrency for free — one thread, asleep in epoll_wait, holding tens of thousands of connections — and no parallelism at all. The three modules under Parallelism are the only ways to opt into it, which is also why none of them speeds up work that was never blocking your thread.

11 Core concepts · the ideas that span every layer

The event loop, backpressure, workers — the things that aren't boxes.

These are not components; they are the ideas that thread through all four layers at once, and they are where most Node misunderstandings live. Open any one to see how it connects — and, where there is one, to watch its journey.

Glossary
Back to JIT-Atlas