Published September 5, 2026
6 min read
Intermediate
Backend & CloudInteractive visualizer

What Happens When Your App Makes an HTTP Request?

Follow fetch through DNS, connection security, edge infrastructure, authentication, cache, database, serialization, retries, and the final UI update.

Interactive Request Journey

Open in Engineering Lab
Loading visualizer...

This line hides an entire distributed system:

TypeScript
const response = await fetch("https://api.example.com/profile");

The request may cross caches, networks, trust boundaries, runtime isolates, application code, and storage before a Promise settles. Understanding those stages makes latency, failures, retries, and security easier to design.

The simulator uses illustrative latency so differences remain visible. Those values are not universal benchmarks: geography, cache state, protocol reuse, radio conditions, load, query plans, and provider architecture can change every number.

1. Parse the URL and create a request

The client separates the scheme, hostname, port, path, and query. The https scheme selects protected transport behavior. Headers may add content negotiation, tracing, cache conditions, and an access token. The app should also decide cancellation, timeout policy, and which errors become visible UI states.

fetch resolves for HTTP responses such as 404 or 500; it normally rejects for network-level failures or cancellation. Check response.ok or the status explicitly.

TypeScript
const controller = new AbortController();

const response = await fetch(url, {
  headers: { Authorization: `Bearer ${session.accessToken}` },
  signal: controller.signal,
});

if (!response.ok) {
  throw new HttpError(response.status);
}

2. Resolve the hostname

DNS maps a hostname to connection information, often using caches in the application, operating system, local network, resolver, and authoritative DNS chain. A cache hit may make this nearly invisible. A miss, slow resolver, or broken network can make DNS a significant failure stage.

CDN providers may return addresses that route the user toward a nearby edge. “Nearby” is a routing outcome, not a guarantee that the application's origin or database is equally close.

3. Establish or reuse a connection

For a new HTTPS connection, the client and server establish transport and cryptographic state, validate the certificate chain and hostname, and negotiate a protocol. Existing pooled connections can skip much of this setup.

HTTP/2 can multiplex concurrent streams over one TCP connection. HTTP/3 carries HTTP semantics over QUIC and can improve behavior under some loss and network-change conditions. Neither protocol makes application latency disappear: requests still need routing, authorization, computation, and data.

TLS provides confidentiality and integrity in transit and authenticates the server under the certificate model. It does not prove that a client's JSON claims are honest. That is an application trust decision.

4. Reach the edge

A CDN or edge platform such as Cloudflare can terminate connections, apply firewall or rate-limit rules, serve cached responses, and route misses to a Worker or origin. A cache hit is powerful because it removes entire downstream stages.

Cache keys must account for content variation. Caching a personalized response without correctly varying by identity or authorization can leak one user's data to another. Private responses often need conservative cache headers or an explicitly partitioned application cache.

5. Authenticate, authorize, and validate

The backend verifies the presented credential, derives an identity, checks whether that identity may perform this action, and validates the untrusted request shape. Authentication answers “who is this?” Authorization answers “may this identity do this?” They are not interchangeable.

Do cheap rejection before expensive database or third-party calls when possible. The secure Cloudflare API tutorial develops input validation, durable rate limiting, and signed state in detail.

6. Run application logic

The handler coordinates domain rules rather than simply translating JSON into SQL. It may check a cache, query Supabase/PostgreSQL, call another service, apply a transaction, or enqueue background work.

For reads, caching can reduce latency and load, but freshness needs a policy: time-based expiry, versioned keys, explicit invalidation, or revalidation. For writes, correctness usually requires transactions, constraints, idempotency, and clear ownership of state.

Slow database access may come from missing indexes, poor estimates, lock waits, too many round trips, connection exhaustion, or returning far more data than the screen uses. Database Indexes Visualized isolates the page-and-index part of that problem.

7. Serialize and return the response

The server turns values into response bytes and selects status, content type, cache headers, and compression behavior. Serialization consumes CPU and large payloads consume bandwidth. Returning a narrow DTO can be safer and faster than exposing an entire database row.

The response travels back through the connection. Intermediaries and the client may cache it according to HTTP rules. The network is not necessarily symmetric: upload and download routes, radio scheduling, congestion, or packet loss can differ.

8. Parse and update the UI

await response.json() parses bytes into JavaScript values. For a large response that parsing can be meaningful CPU and allocation work. Then state management determines how much of the UI re-renders.

TypeScript
try {
  setState({ status: "loading" });
  const profile = await getProfile({ signal });
  setState({ status: "ready", profile });
} catch (error) {
  if (isAbortError(error)) return;
  setState({ status: "error", error: toUserSafeError(error) });
}

Guard against stale responses when the user changes screens or issues a newer request. Cancellation saves work when supported; a request identifier or state-machine transition can prevent an older result from overwriting newer UI state.

Errors happen at every layer

DNS can fail. Certificates can be invalid. Connections can time out. The edge can reject a request. Authentication can expire. Validation can return 400. Rate limits can return 429. Dependencies can fail. JSON can be malformed. The app can unmount before the result arrives.

Model these as distinct outcomes where recovery differs. A sign-in prompt is not the same as an offline banner; a validation error should not be retried like a transient 503.

Retry with semantics, not optimism

Retries multiply load during an outage. Use bounded attempts, exponential backoff, jitter, and respect server guidance such as Retry-After. Retry only failures likely to be transient.

Safe reads are usually retryable. Mutations need idempotency or another deduplication rule because the client may lose the response after the server commits. Designing Retry-Safe APIs shows how a durable idempotency key turns duplicate delivery into one logical operation.

Latency is a budget

Total wait is the composition of connection setup, geographic travel, queueing, application execution, dependency access, serialization, transfer, parsing, and rendering. Optimizing a 2 ms function cannot repair a 600 ms query. Moving code to an edge location may reduce user-to-compute distance while increasing compute-to-database distance.

Trace across boundaries with a correlation ID, server timing, structured logs, database plans, and client measurements. Percentiles show tail behavior that averages hide. Measure cache hits and misses separately.

Key takeaways

  • One fetch crosses networking, security, application, data, and UI layers.
  • Connection reuse and caching can remove expensive stages, but both need correct keys and policies.
  • Authentication, authorization, and validation protect different boundaries.
  • HTTP status failures and network failures surface differently in fetch.
  • Retries require backoff and idempotency, especially for mutations.
  • Optimize the stage that dominates measured latency rather than the stage easiest to change.