Internet Request Paths for Backend Developers

LESSON

Backend Development Foundations

001 25 min beginner

Internet Request Paths for Backend Developers

By the end of this lesson, you will be able to...

  • Trace one HTTPS request from browser URL to backend handler and back.

  • Match common symptoms, such as DNS failure, TLS failure, 404, 502, and 504, to the boundary that can explain them.

  • Choose useful evidence before changing backend code.

Idea in one sentence: A backend request is a chain of boundaries, and good debugging starts by finding which boundary last saw the request correctly.

Core Insight

A small team ships its first public orders API.

Locally, the route works:

GET http://localhost:3000/orders/123

After deployment, users open:

https://api.example.test/orders/123

Some users get JSON. Some see a browser error. One test returns 502 Bad Gateway. Another returns 504 Gateway Timeout. The backend logs show nothing for a few failed requests.

The naive idea is simple:

The frontend calls the backend.

That sentence is useful in a product meeting. It is too blurry for debugging.

A browser cannot call a Python, Go, Ruby, Java, or Node handler directly. It resolves a name, opens a connection, checks encryption, sends HTTP bytes, crosses a proxy, reaches a runtime, enters framework routing, runs middleware, calls a handler, waits for dependencies, and receives a response through the same kind of path.

The useful model is:

client intent -> network path -> edge boundary -> application boundary -> dependency boundary -> response

Each boundary can see different evidence. Each boundary can also fail in a different way.

That is why this is the first backend lesson. Before a backend developer can fix a handler, choose a framework, tune a database, or deploy a service, they need one durable habit:

Trace the request path before choosing the fix.

The Production Symptom

Use one request through the lesson:

GET https://api.example.test/orders/123

The user expects:

200 OK
Content-Type: application/json

{"id": "123", "status": "paid"}

The deployed system is small:

browser
  -> DNS
  -> TCP connection
  -> TLS
  -> HTTP request
  -> edge proxy
  -> orders service
  -> web framework route
  -> handler
  -> database

At first, this looks like a lot of parts for one beginner lesson. The trick is not to memorize every protocol detail. The trick is to ask a plain question at each step:

What could this part see, change, wait for, or decide?

DNS can see a host name and return an address. It cannot see your route handler.

TLS can check whether the certificate matches the host. It cannot decide whether order 123 exists.

The proxy can receive an HTTP request and forward it to an upstream service. It may not know why the handler is waiting unless the application logs or traces say so.

The handler can decide whether the order exists. It cannot fix a browser that never resolved the host name.

So far, the request is not a single jump. It is a sequence of ownership boundaries.

Boundary Evidence

Plain meaning:

Evidence is something a component records or shows because the request reached that component.

In this scenario:

A proxy access log proves the request reached the proxy. A backend route log proves the framework matched a route. A database timing entry proves the handler reached the database call.

Technical name:

We will call this boundary evidence: evidence that a request crossed, failed at, or waited at a specific boundary.

This term matters because user symptoms are often vague. A user may say "the API is down" for all of these:

the browser cannot resolve the host
the certificate is invalid
the proxy cannot connect to the service
the framework has no matching route
the handler is waiting on a slow database query

Those are different failures. They need different fixes.

The beginner mistake is to change the code path they know best. If you mostly write handlers, you may open the handler first. If you recently changed deployment, you may blame the proxy first. Boundary evidence slows down that reflex in a useful way.

It asks:

What is the last boundary where the request still looked healthy?
What is the first boundary where it looked wrong?

A Worked Request Trace

Trace the successful request before looking at failures.

Input:
  GET https://api.example.test/orders/123

Step 1: URL parsing
  scheme=https
  host=api.example.test
  path=/orders/123

Step 2: DNS
  api.example.test -> 203.0.113.10

Step 3: TCP
  client opens a connection to 203.0.113.10:443

Step 4: TLS
  server presents a certificate valid for api.example.test

Step 5: HTTP
  client sends GET /orders/123 with headers

Step 6: edge proxy
  proxy receives host=api.example.test path=/orders/123
  proxy adds request_id=req-481
  proxy forwards to orders-service:3000

Step 7: framework
  framework matches GET /orders/:id
  middleware attaches auth and request context

Step 8: handler
  handler reads id=123
  handler asks database for order 123

Step 9: database
  database returns one row

Output:
  200 OK with JSON body

Now make the intermediate evidence visible:

Boundary Healthy evidence What it can prove What it cannot prove
DNS host resolves to an address the client found where to connect the backend handler is correct
TLS certificate accepted the encrypted connection can start the route exists
proxy access log with request ID the edge received HTTP the database is fast
framework route log for GET /orders/:id application routing matched the proxy timeout is high enough
handler structured log with id=123 application code started work DNS was fast for the user
database query timing for request ID dependency work happened response reached the browser

This table is not just for incidents. It is a design tool. A backend that cannot produce boundary evidence is harder to operate, even if the code is correct.

So far, we have built the central model: a request path is a chain, and each link owns a different kind of evidence.

Where The Naive Model Breaks

Now replay the same user complaint with four different failures.

Symptom 1:
  Browser says ERR_NAME_NOT_RESOLVED.

Path:
  browser -> DNS fails -> no TCP -> no TLS -> no HTTP -> no proxy log -> no app log

First useful evidence:
  DNS or browser network output.

Likely first owner:
  domain, DNS record, local resolver, or environment name.

The backend may be perfectly healthy. It never got a chance to be wrong.

Symptom 2:
  Browser warns that the certificate is invalid.

Path:
  browser -> DNS ok -> TCP ok -> TLS rejected -> no HTTP request -> no handler log

First useful evidence:
  certificate details, TLS error, host mismatch, expiry, trust chain.

Likely first owner:
  certificate configuration or edge TLS setup.

The route handler still has no useful opinion. The request body was not sent to it.

Symptom 3:
  Response is 502 Bad Gateway.

Path:
  browser -> DNS ok -> TLS ok -> proxy receives HTTP -> proxy cannot reach upstream

First useful evidence:
  proxy log, upstream name, connection refused, service health, port.

Likely first owner:
  service process, container networking, port binding, or proxy upstream config.

This connects directly to the next lesson. A backend service must start as a process and listen on the expected port before the proxy can forward requests to it.

Symptom 4:
  Response is 504 Gateway Timeout.

Path:
  browser -> proxy -> framework -> handler -> database waits too long -> proxy gives up

First useful evidence:
  proxy duration, upstream timeout, app request ID, database query timing.

Likely first owner:
  slow handler path, slow dependency, missing index, timeout mismatch, or overload.

In this case the proxy produced the visible response, but the underlying cause may sit behind the application boundary.

Check: The backend logs show no request, and the browser says the host cannot be found. Should you inspect the handler first?

Think first, then reveal.

Answer: No. The request failed before the client could open a connection. Start with DNS evidence. The handler may still be correct.

Reading Logs As A Path

Request IDs make boundary evidence easier to connect.

A request ID is a short value attached near the edge and carried through logs:

edge:
  request_id=req-481 path=/orders/123 method=GET

proxy:
  request_id=req-481 upstream=orders-service:3000 upstream_status=200 duration_ms=42

app:
  request_id=req-481 route="GET /orders/:id" order_id=123

database:
  request_id=req-481 query=find_order_by_id duration_ms=18

edge:
  request_id=req-481 status=200 total_ms=64

This is a healthy path. You can follow one request from edge to application to dependency and back.

Now compare a broken path:

edge:
  request_id=req-913 path=/orders/123 method=GET

proxy:
  request_id=req-913 upstream=orders-service:3000 upstream_status=timeout duration_ms=30000

app:
  request_id=req-913 route="GET /orders/:id" order_id=123

database:
  request_id=req-913 query=find_order_by_id duration_ms=28650

edge:
  request_id=req-913 status=504 total_ms=30001

The visible response is 504, so the proxy made the final timeout decision. But the trace also shows the application spent almost all of the time waiting for the database.

That difference matters. If you only raise the proxy timeout, the user may wait longer and still get a bad experience. If you only rewrite the route, you may miss the slow query. The trace tells you where to ask the next question.

Check: In the broken path above, what is the first mitigation you would consider: increase every timeout, or inspect the slow database call?

Think first, then reveal.

Answer: Inspect the slow database call first. A timeout increase may hide the symptom, but the trace says most of the request time is spent at the database boundary.

Common Confusions

Confusion: "No backend log means the backend is innocent"

Why it is tempting:

If the application did not write a log line, it feels natural to say the backend was not involved.

Better model:

No application log only means the request did not reach the logging point you are checking. That could mean the request failed before the app. It could also mean the app crashed before logging, the log level filtered the line, the request ID was not propagated, or the log pipeline dropped the event. Use the missing log as evidence, but do not turn it into a conclusion by itself.

Ask one more question:

What is the closest earlier boundary that definitely saw this request?

If the proxy saw it and the app did not, inspect forwarding, process health, port binding, and application startup. If the proxy did not see it either, move outward to DNS, connection, TLS, or client behavior.

Confusion: "The component that returns the status code is always the root cause"

Why it is tempting:

The visible status code feels like the source of truth. If the user sees 504 Gateway Timeout, the proxy must be the problem. If the user sees 500, the handler must be the whole problem.

Better model:

The component that returns the visible result owns the last decision. It may not own the deeper cause. A proxy may return 504 because a backend dependency was slow. A handler may return 500 because a required environment variable was missing at startup and the app fell into a bad fallback. The status code tells you where the failure became visible. The trace tells you where to look next.

Confusion: "More logs always make debugging better"

Why it is tempting:

When you are missing evidence, the obvious reaction is to record everything.

Better model:

More logs can help, but unfocused logs create noise and risk. Full request bodies may contain private data. Full tokens are secrets. High-cardinality fields can make storage expensive. Repeated logs inside loops can hide the one boundary decision you needed.

Boundary evidence is narrower. It records the crossing, duration, decision, status, and correlation ID. It tries to answer "where did the request go?" before it tries to describe every byte.

Trade-offs And Limits

Tracing request paths buys clarity. It reduces random fixes. It helps a beginner avoid the most common production mistake: changing code before knowing whether the request reached the code.

It also has costs.

The main trade-off is debugging clarity versus instrumentation cost and privacy risk.

Instrumentation takes work. Logs and traces need names, request IDs, timestamps, status codes, and duration fields. Too little evidence leaves you guessing. Too much evidence can become noisy, expensive, or unsafe.

The boundary rule is a useful compromise:

Record enough to know that a request crossed a boundary,
how long it spent there,
what decision happened,
and which identifier connects it to the next boundary.

Do not log secrets. Do not log full authorization tokens. Do not dump private request bodies just because debugging is hard. Useful evidence should explain the path without exposing the user.

There is another limit. Real systems may add CDNs, caches, queues, retries, service meshes, background jobs, and multiple downstream services. This lesson uses a direct HTTPS request because it gives you the base mental model. Later backend lessons add process startup, databases, frameworks, validation, authentication, testing, deployment, and observability.

The habit remains the same:

Find the boundary.
Ask what it could see.
Collect the evidence.
Then choose the fix.

Practice

Trace this request:

POST https://api.example.test/login

The browser receives:

502 Bad Gateway

The proxy log says:

request_id=req-222 upstream=auth-service:8080 error=connection_refused

The application log for req-222 has no entry.

Write a short diagnosis using the request-path model.

A good answer should mention:

Resources

Key Takeaways

NEXT Command Line, Processes, and Environment Variables