The Anatomy of a Request
DNS, TCP, TLS, a kernel queue, the Node.js event loop, and a database: everything that happens in the roughly 200 milliseconds between a click and a response.
The Click
Somewhere, a browser tab is sitting on a blank line, a form is about to submit, or a script fires a fetch(). The instant it does, a clock starts.
A typical request to a server on the other side of a continent, DNS lookup, TCP handshake, TLS handshake, a database round trip, and the response coming back, lands somewhere around 200 milliseconds. That's faster than a human blink. It's also seven distinct systems, each with its own protocol, each capable of failing in its own specific way.
Scroll to follow one request through all seven.
The Address
Before anything can be requested, the browser needs an IP address. If nothing's cached, it asks a recursive resolver, often the OS's configured DNS server, sometimes a public one like 1.1.1.1.
The resolver doesn't know the answer either. It asks a root nameserver, which doesn't know the answer but knows who does for .com. That TLD nameserver doesn't know the final answer either. It points to the domain's authoritative nameserver, the one place that actually holds the A record.
Four hops just to get an IP address, before a single byte of the actual request has gone anywhere. The resolver caches the answer for the record's TTL, so most requests skip straight past this, but the first one always pays the full price.
The Handshake
IP address in hand, the client can finally talk to the server, but not yet send data. TCP needs a reliable, ordered connection first, and that takes a three-way handshake (RFC 9293).
The client sends a SYN with an initial sequence number. The server replies with its own SYN plus an ACK of the client's number. The client acknowledges the server's number back. Both sides now agree on starting sequence numbers in both directions: the basis TCP uses to detect lost or out-of-order packets for the rest of the connection.
This costs a full round trip before a single byte of the actual HTTP request has been sent. On a connection with 40ms of latency each way, that's 80ms gone before "the request" has even started, which is exactly why keeping a TCP connection open across multiple requests (HTTP keep-alive) matters as much as it does.
The Lock
The TCP connection is open, but it's plaintext. Before HTTP traffic can flow, TLS 1.3 (RFC 8446) negotiates encryption keys, and it does it in a single round trip, half the cost of TLS 1.2's two-round-trip handshake.
The client sends a ClientHello that already includes a guessed key share. If the server supports that key exchange group, it replies with everything at once: its own key share, its certificate, and a Finished message, all in one flight. The client verifies the certificate, derives the same shared secret, and sends its own Finished.
From the client's Finished message onward, every byte on this connection is encrypted, including the HTTP request that's about to be sent. Two round trips have now been spent (TCP, then TLS) before the actual request exists on the wire.
The Gate
The three-way handshake finished inside the kernel, not inside Node.js. While the connection was half-open, it sat in the kernel's SYN queue. The moment the final ACK arrives, the kernel moves it into the accept queue, a completed connection, waiting for an application to claim it.
The size of that queue is the backlog argument Node's server.listen() passes down to the kernel's listen() syscall. If connections complete their handshake faster than the application calls accept(), the accept queue fills up and new connections get dropped: a real, common way a server "goes down" under load without ever running out of CPU.
Node's own event loop (not this request specifically, the loop itself) periodically calls accept(), pulls this connection out of kernel space, and hands it a file descriptor in user space. Only now does JavaScript get involved.
The Loop
Node.js runs one event loop, cycling through six phases in a fixed order: timers, pending callbacks, an internal idle/prepare phase, poll, check, and close callbacks. It keeps cycling for the lifetime of the process, not once per request.
Our connection's readable-socket event fires during the poll phase, the phase that retrieves new I/O events and runs almost all I/O callbacks (timers and setImmediate() are handled separately, in their own phases). That's where the route handler actually starts executing.
One detail trips people up constantly: process.nextTick() isn't a phase at all. Its queue, along with resolved Promise microtasks, drains completely between every phase transition, before the loop is allowed to move on. Queue enough nextTick() calls recursively and the loop never reaches poll, timers, or anything else: a real, self-inflicted way to starve I/O.
The Answer
The route handler needs data, so it issues a query over the Postgres wire protocol, almost certainly through a connection already open in a pool, because paying for a fresh TCP and TLS handshake to the database on every request would double everything Acts 3 and 4 just cost, per query.
Postgres returns rows. Node serializes them into a response body (JSON, typically) and writes it back to the same socket the request arrived on. Critically, this doesn't cost another TLS handshake either: it's the same encrypted session from Act 4, just carrying traffic in the other direction now.
The browser receives the bytes, decrypts them with keys it already has, parses the response, and hands it to whatever called fetch() in the first place. Somewhere around 200ms after the click, seven systems have each done their job exactly once.