Chapter 6: Node.js Architecture
Node.js Architecture — written as if we are sitting together with a whiteboard, drawing layers, arrows, and boxes while I explain how everything actually fits together in 2025–2026.
I will try to make it very visual in text form, very concrete, and I will show the most important concepts with small code examples where it helps understanding.
1. The Big Picture – One clear layered diagram
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
┌───────────────────────────────────────────────────────────────┐ │ Your JavaScript Code │ │ (async/await, promises, classes, functions, modules…) │ └───────────────────────────────┬───────────────────────────────┘ │ ┌─────────▼─────────┐ │ V8 Engine │ ← executes JS │ (Ignition + TurboFan)│ └─────────┬───────────┘ │ ┌─────────▼─────────┐ │ Node.js Core │ ← C++ glue + JS bindings │ (libuv, http, fs, │ │ crypto, events…) │ └─────────┬───────────┘ │ ┌──────────────┼──────────────┐ │ │ │ ┌────────▼────────┐ ┌────▼────┐ ┌────▼────┐ │ libuv │ │ V8 │ │ Other │ │ (event loop, │ │ bindings│ │ C/C++ │ │ thread pool, │ │ │ │ libraries│ │ async I/O) │ └─────────┘ └─────────┘ └─────────────────┘ |
Four major layers (from top to bottom):
- Your Application Code (what you write)
- V8 JavaScript Engine (executes your JS)
- Node.js Core (C++ + JavaScript glue code)
- libuv + lower-level libraries (OS-level I/O, threads, networking…)
2. Layer by layer – detailed explanation
Layer 1 – Your JavaScript Code
This is what you actually write:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import express from 'express'; const app = express(); app.get('/users', async (req, res) => { const users = await db.findMany(); // ← async I/O res.json(users); }); app.listen(3000); |
Everything here runs single-threaded inside one V8 instance.
Layer 2 – V8 Engine
Responsible for:
- Parsing your JavaScript
- Compiling to bytecode
- Optimizing hot functions (TurboFan)
- Managing memory (Orinoco garbage collector)
- Executing your synchronous code + calling async callbacks
Important facts 2025–2026:
- One V8 instance per Node.js process (unless using Worker Threads)
- Single JavaScript thread per process
- Very good at optimizing predictable code
- Very bad at long synchronous CPU work
Layer 3 – Node.js Core (the “glue”)
Written mostly in C++, with some JavaScript.
This layer does two crucial jobs:
- Exposes C++ functionality to JavaScript (http parser, file system, crypto, zlib, dns, tls…)
- Integrates libuv into V8’s event loop
Most important built-in modules come from here:
- fs (file system)
- http / https
- net (TCP sockets)
- stream
- crypto
- zlib
- events
- buffer
- process
- child_process
Example: when you write
|
0 1 2 3 4 5 6 7 |
import { readFile } from 'node:fs/promises'; const data = await readFile('big.json'); |
→ Node.js core forwards this call to libuv
Layer 4 – libuv (the real magic of Node.js)
libuv is a C library that provides:
- Event loop (the heart)
- Asynchronous I/O operations
- Thread pool for blocking tasks
- Cross-platform abstractions (Windows IOCP, Linux epoll/kqueue, etc.)
libuv has four main responsibilities in Node.js:
| Responsibility | Handled by libuv? | Examples | Thread? |
|---|---|---|---|
| Timers | Yes | setTimeout, setInterval, setImmediate | Main thread |
| I/O polling (network) | Yes | Incoming HTTP requests, TCP/UDP | Main thread |
| File system operations | Yes | readFile, writeFile, readdir | Thread pool |
| DNS lookups | Yes | dns.lookup, getaddrinfo | Thread pool |
| CPU-intensive crypto | Yes | crypto.pbkdf2, crypto.scrypt | Thread pool |
| Child process spawning | Yes | spawn, fork, exec | Separate |
| Signal handling | Yes | SIGINT, SIGTERM | Main thread |
Key insight:
Most blocking operating system calls are offloaded to a thread pool → so your JavaScript thread (event loop) stays free to handle other requests
Default thread pool size = 4 threads
You can change it:
|
0 1 2 3 4 5 6 |
UV_THREADPOOL_SIZE=16 node server.js |
or in code:
|
0 1 2 3 4 5 6 |
process.env.UV_THREADPOOL_SIZE = '16'; |
3. How the Event Loop actually works (simplified but accurate)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
┌───────────────────────────────┐ │ Timers │ ← setTimeout, setInterval └───────────────┬───────────────┘ │ ┌───────────────▼───────────────┐ │ Pending Callbacks │ ← most OS async callbacks └───────────────┬───────────────┘ │ ┌───────────────▼───────────────┐ │ Idle, Prepare │ ← internal └───────────────┬───────────────┘ │ ┌───────────────▼───────────────┐ ← the most important phase! │ Poll │ ← retrieve new I/O events └───────────────┬───────────────┘ (network, file system…) │ ┌───────────────▼───────────────┐ │ Check │ ← setImmediate callbacks └───────────────┬───────────────┘ │ ┌───────────────▼───────────────┐ │ Close Callbacks │ ← socket.on('close'), etc. └───────────────────────────────┘ |
Very important order in real applications:
- Timers (setTimeout, setInterval)
- I/O callbacks (network reads/writes, file reads…)
- setImmediate
- Close events
Classic interview / debugging question:
|
0 1 2 3 4 5 6 7 8 9 |
setTimeout(() => console.log('timeout'), 0); setImmediate(() => console.log('immediate')); // Which prints first? |
Answer: It depends — but in practice setImmediate often wins on Windows/Linux when run from I/O phase.
4. Summary table – Node.js Architecture at a glance
| Layer / Component | Language | Main responsibility | Single-threaded? | Can block event loop? |
|---|---|---|---|---|
| Your application code | JavaScript | Business logic, routing, async flows | Yes | Yes (if sync CPU work) |
| V8 Engine | C++ | Parse, compile, optimize & execute JS | Yes | Yes |
| Node.js Core bindings | C++ + JS | Connect JS ↔ libuv / native libraries | Yes | No (delegates) |
| libuv – Event Loop | C | Orchestrates timers, I/O polling | Yes | No |
| libuv – Thread Pool | C | File system, DNS, some crypto | No (4+ threads) | No (offloaded) |
| Operating System | — | Real networking, file system, threads | — | — |
5. Quick real-world implications
| Situation | What happens in Node.js architecture | Recommendation / Fix |
|---|---|---|
| Long synchronous for-loop (10 seconds) | Blocks entire event loop → server freezes | Use Worker Threads or break into setImmediate |
| Reading 1000 small files | Goes to thread pool → usually fine | Increase UV_THREADPOOL_SIZE if bottleneck |
| Handling 10,000 concurrent requests | Event loop stays responsive (I/O is async) | Good – this is Node’s strength |
| Heavy image processing in route handler | Blocks event loop → very bad | Offload to worker thread or external service |
| Doing crypto.pbkdf2 10,000 times | Uses thread pool → acceptable | Still better to batch or offload if possible |
Would you like to go deeper into any specific part?
Popular next topics people usually ask after this:
- Detailed walkthrough of one full event loop tick with real code
- How Worker Threads fit into this architecture
- What happens when you do cluster.fork()
- How streams interact with libuv
- Common performance bottlenecks seen in production
- Comparison: Node.js architecture vs Go / Java / Rust / Bun
Just tell me which direction feels most useful — I’ll continue with drawings, code, and concrete examples. 😄
