00 Field guide · Node.js on Linux
Watch one request travel from your handler to the wire.
Node is four stacked layers with two narrow gates between them. Your JavaScript sits at the top and cannot do any I/O by itself — every read, every socket, every timer funnels down through Node's JavaScript API, across the C++ boundary into V8 and libuv, and then across the syscall boundary into the kernel. Pick a journey below and press play: one unit of work threads its way down and back, lighting each part it touches. The two things worth carrying away are that only one thread runs your JavaScript, and that files and sockets take completely different routes.
Pick a journey and press play
Each frame moves the work-token to the part doing the work and lights everything it has passed. Blue is your code, green is Node's JavaScript API, purple is the native core of C and C++, teal is the operating system, and the amber gates are the only two crossings between those worlds. The journey chips below are coloured by what carries the work: green journeys 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.
01 The mental model
Four layers, and the two gates that actually matter.
Almost every confusing thing about Node follows from one arrangement. Your code and Node's own JavaScript both run on a single thread, in a single V8 isolate, with no ability to touch a file or a socket. Beneath them sits the native core — V8 itself, libuv, and a handful of vendored C libraries — and beneath that, the kernel.
Between each pair sits a gate. 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.
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 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.
04 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.
05 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.
06 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.
07 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.
08 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.
09 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.