Chapter 2 of 18

The network

How a web app works: browsers, servers, requests, databases, and what happens between them.

Ask an agent for a web app, a program you use through a browser such as an online shop, and it will build one, and it will run. If you can't see how the pieces fit, all you can do is click around, and when something breaks all you can say is that it's broken. "Handle errors better" gets you some code. "Return a 429 with a Retry-After header after five failed logins" gets you the thing you wanted, and you can check that it arrived. By the end of this chapter you will be able to read that second sentence, and write your own.

A web app is a smaller idea than it looks. One program asks, another answers, and they talk in a plain format you can read yourself. Nearly everything else is about where a piece of code runs, who is allowed to see it, how far the message has to travel, and what happens when part of the journey fails.

This chapter follows one request all the way through, from the address you type to the database and back. On the way you pick up the words for each part. Most of the instruments below use the real thing: your browser's own URL parser, its real network log, real encryption, and real signatures.

In any exchange over a network, one program asks and the other answers.

Client and server

A client asks and a server answers. A server is only a program on a computer somewhere, waiting to be asked. Your browser is a client. So is a phone app, or a script you run in a terminal.

Agents will happily put code on the wrong side of this line, and the result still runs. When you read what one built, the first question is which machine each piece runs on, because that decides who can see it and who can change it.

Say it to your agent

“Before you change anything, list every file you created in a table with three columns: the path, whether it runs on the server or in the browser, and which secrets or data it can reach.”

Browser

The program that turns an address into a page. It asks servers for files, draws what comes back, and runs the code that came with it. Chrome, Safari and Firefox are browsers.

An agent reading your code sees what should happen. The browser keeps a log of what did. When something looks broken, that log settles the argument, so ask for its contents instead of a theory about them.

Say it to your agent

“The save button does nothing. Open the page in Playwright, click save, and show me the request it sent and the status it got back, from the network log. Don't work it out from reading the code.”

Frontend and backend

Also called Client side and server side

The frontend is the code that runs in the user's browser. The backend is the code that runs on your server. Anyone can read the frontend and change what it sends. Nobody outside can read the backend.

Agents often put a rule where the button is, because that's where the feature is. A check in the frontend is only a courtesy to honest people, so anything that protects money or data has to be repeated on the backend.

Say it to your agent

“Work out the order total on the server from the product ids, and ignore any price the browser sends. Add a test that posts total: 1 and checks the real price is charged. Then grep the build output in dist/ for any secret key.”

A request starts with an address, and every part of the address is read by something different.

URL

Also called Web address

The address of one thing on the web. https://shop.example.com:443/cart?coupon=SAVE#total has a scheme, a host, a port, a path, a query and a fragment. Each part is read by something different.

Naming the part gets you the right change. "Read the page number from the query string" and "put it in the path" are different pieces of work. Agents that build or compare URLs by gluing text together also produce a family of bugs that send people to the wrong place, and you can now spot them.

Say it to your agent

“Read the page number from the query string as ?page=2, default it to 1, and keep it when the user changes the filter. Build the URL with new URL() and searchParams, not by joining strings. For redirects, compare the parsed hostname to an allowlist.”

DNS

Also called Domain Name System

The internet's address book. It turns a name like shop.example.com into the numeric address of a computer. Answers get saved along the way, and each one says how long it may be kept, which is called its TTL.

Agents can edit a DNS record in seconds and then tell you it's done, while people keep arriving at the old server. The waiting is decided before the change, by the TTL already out there, so a move needs planning days ahead.

Say it to your agent

“We're moving servers on Friday. Today, lower the TTL on the A record for shop.example.com to 300. On Friday, change the record, keep the old server running for at least the old TTL, and show me dig shop.example.com +noall +answer before and after.”

IP address and port

An IP address identifies a computer on a network, like 203.0.113.10. A port number picks out one program on that computer, because many can be listening at once. Together they say exactly where a message goes.

The errors an agent hits while running your app are often about this and nothing else. "Address already in use" means two programs want one port. "Connection refused" means nothing is listening there. Neither is a bug in the code you just changed.

Say it to your agent

“The dev server won't start because port 3000 is in use. Run lsof -i :3000, tell me which process holds it and what started it, and ask me before you kill anything.”

Localhost

Also called 127.0.0.1, Loopback

A name that always means this same computer. A server on localhost:5173 answers you and nobody else, because for anyone else the same name means their own machine.

An agent saying "it's running on localhost" has only told you about its own machine. If it runs in a container (a sealed-off environment on your machine) or in the cloud (on someone else's computer), its localhost isn't yours and you won't see a thing. Working on localhost also says little about production, where there's real distance, real encryption and a different server.

Say it to your agent

“Start the dev server and give me the exact URL it printed, because Vite picks another port if 5173 is taken. Then curl that URL and show me the status code before you tell me it works. Don't bind to 0.0.0.0.”

TCP connection

Also called TCP

A reliable conversation between two programs across a network. Before any data moves, the two sides exchange messages to open the connection, and that costs one full trip there and back.

Opening a connection is expensive, and you pay for it per connection, which agents rarely think about. Code that opens a new database connection for every request can look perfectly clean and still fall over when many people use it at once.

Say it to your agent

“Don't create a new database client inside the request handler. Create one at startup and reuse it through a pool. Tell me the pool size you chose and how it compares to the database's connection limit.”

Now the conversation itself. It is more readable than you might expect.

HTTP

The agreed format browsers and servers use to talk. One side sends a request, the other sends back a response. It remembers nothing between requests, so each one has to carry everything the server needs to know.

Almost everything your agent builds on the web goes through it, including the agent's own calls to its model. Reading one raw exchange is the fastest way to find out which side is wrong, and it replaces a lot of guessing by either of you.

Say it to your agent

“Show me the raw HTTP exchange: run curl -i against the endpoint and paste the status line, the headers and the body. I want the output itself, not your description of it.”

Request and response

A request is a method (a verb such as GET), a path, some headers (labelled lines of extra information) and maybe a body (the content). A response is a status code (a number saying how it went), some headers and maybe a body. Every feature on the web is built from those two shapes.

Saying "it's broken" gives an agent nothing to hold. Splitting the bug in two does: if the request was wrong, the problem is in the frontend, and if the response was wrong, it's in the backend. That cuts the search in half before anyone reads a line of code.

Say it to your agent

“Before you change anything, log the exact request the page sends and the exact response it gets back, and paste both. Then tell me whether the bug is in the request or in the response, and why.”

You and your agent need a way to send a request without a browser in the way.

curl

A command that sends one HTTP request from the terminal and prints what comes back. It takes the browser out of the question, so it tells you whether the server itself works.

It gives an agent a cheap way to prove a claim, and it gives you output you can read. One trap: curl exits 0 even when the server answers with an error, because the transfer itself worked. Use it in a script without --fail and your check can never fail.

Say it to your agent

“Prove it with curl. Run curl -i -X POST localhost:3000/api/notes -H 'Content-Type: application/json' -d '{"text":"hi"}' and paste all of it. Then run it again without the cookie and show me the 401.”

Every request opens with one word, and that word makes a promise about what happens if it gets sent twice.

HTTP method

Also called HTTP verb, GET and POST

The verb at the start of a request. GET reads, POST creates or submits, PUT replaces, DELETE removes. The method also tells everything between you and the server whether it's safe to send the same request again.

Requests get repeated, by double taps, flaky networks and helpful retry code. An agent that uses GET for an action can have it fired by a link preview, and one that writes a plain POST checkout can charge someone twice. Neither shows up in a test where everything goes right.

Say it to your agent

“This checkout POST can run twice if the user double-taps. Require an Idempotency-Key header, store the first response against that key for 24 hours, and add a test that sends the same key twice and gets one order and one charge.”

The answer opens with a number, and it plays the same part here that the exit code played in the terminal.

Status code

Also called HTTP status

A three-digit number at the top of every response. 2xx means it worked, 3xx means look elsewhere, 4xx means the request was wrong, and 5xx means the server failed. It works like an exit code for the web.

This is the word that sharpens your instructions most. Left alone, agents often catch an error and return 200 with an error message inside, which reads as success to every client, cache and graph. Asking for the exact code fixes all of them at once.

Say it to your agent

“Add a 429 with a Retry-After header to POST /login after 5 failed attempts per IP address per minute. Return 401 for a wrong password and 422 for a missing email, never a 200 with an error in the body, and add a test for each status.”

Header

Also called HTTP header

A labelled line of extra information on a request or response, such as Content-Type: application/json. Names ignore upper and lower case. Many travel with every request, and you never see them unless you look.

One header is often the whole fix, and it can save an agent writing a page of code: caching, content type, a redirect, a login challenge. Ask for it by name. Code running in the page can't set them all either, so some header bugs can't be fixed in the frontend at all.

Say it to your agent

“Add Cache-Control: no-store and Content-Type: application/json to every response from /api/account, and add a test that makes a real request and fails if either header is missing.”

JSON

A plain text format for data: named values in curly braces (called objects), lists in square brackets, text in double quotes, plus numbers, true, false and null, which means nothing. Most programs on the web send it. Nothing else is allowed, including comments.

Your agent reads and writes JSON all day, including when it asks to use its tools, and its output can be almost right. The strictness is useful: one stray comma fails loudly. Numbers are the quiet exception, because a big id can come back changed with no error.

Say it to your agent

“Send order ids as strings in the JSON, because they go past 2^53. Parse every incoming body with a schema at the edge of the route, and return a 400 with a helpful message when it's malformed.”

Put those together and you have a promise between two programs.

API

Also called Web API, Endpoint

The set of requests a program agrees to answer, and the answers it promises to give. It's a contract between two pieces of software, so that neither side has to read the other's code.

Two ways this goes wrong with agents. They invent endpoints (the separate addresses an API answers at) and fields that sound right but don't exist, so point them at real docs. And they rename a field on your server without knowing who else reads it, and nothing fails until a client shows nonsense.

Say it to your agent

“Don't change the shape of GET /api/orders, because other clients use it. Only add fields. Update openapi.yaml in the same commit, and add a contract test that fails if a documented field disappears or changes type.”

REST

Also called RESTful

A style of designing web APIs around things rather than actions: GET /orders/42, not POST /getOrder. The word comes from Roy Fielding's 2000 dissertation, and most APIs called REST follow only part of it.

It's a convention an agent already knows, so naming it gets you predictable addresses. Don't stop there, though. "Make it RESTful" leaves plenty open, so spell out the addresses and the status codes you want, and argue about the design before the code exists.

Say it to your agent

“Use these routes: GET /orders, POST /orders, GET /orders/:id, PATCH /orders/:id, DELETE /orders/:id. Return 201 with a Location header on create, no verbs in paths, and show me the route list before you build it.”

A server that answers needs somewhere to keep what it knows, and its own memory is the wrong place.

Database, briefly

A program whose whole job is keeping data safe on disk and finding it again quickly. Your server sends it questions over a connection. When your server restarts, its memory is gone, and the database still has everything.

Agents building a quick demo often keep data in a variable, because it runs first time and every check passes. Then a deploy wipes it, or a second copy of the server gives different answers. The "Data and failure" chapter goes much deeper.

Say it to your agent

“You stored signups in an array, so they vanish on restart. Move them to a users table in Postgres, read the connection string from DATABASE_URL, restart the server, and show me the row is still there.”

HTTP forgets you between requests, so being logged in needs a trick.

Cookie

A small piece of text a server asks the browser to keep. The browser then attaches it to later requests to that site, by itself. It's how HTTP, which has no memory, comes to know you.

The security of a login rests on a few words after the value, and they're easy to leave out, because a cookie without them works fine in every test. Asking for them by name gets them. They also explain "works on localhost, not in production".

Say it to your agent

“Set the session cookie with HttpOnly; Secure; SameSite=Lax; Path=/ and no Domain. Add a test that reads the Set-Cookie header from POST /login and fails if any of those is missing.”

Login session

Also called Session, Server-side session

The server's memory of a signed-in user. At login it creates a record and gives the browser a long random id in a cookie. Each later request carries the id, so the server can look up who it belongs to.

An agent asked for "login" will produce something that lets the right person in, and you won't see the gaps by logging in. The questions to ask are whether logging out really ends it, and what happens to a stolen copy.

Say it to your agent

“On login, generate a new session id of at least 64 bits from a secure random source and store the session row on the server. On logout, delete the row, then show me that replaying the old cookie with curl gets a 401.”

Everything so far could be read by anyone along the path, passwords included, unless something hides it.

HTTPS and TLS

Also called SSL

HTTPS is HTTP sent through TLS, an encrypted tunnel. TLS proves you reached the real site, hides what you send, and detects any change on the way. A padlock means the line is private, and says nothing about whether the site is honest.

A certificate is a site's proof of who it is. When a certificate error blocks an agent, the quick way past is to turn checking off, and it may just do that. The app then works and is open to anyone on the path. Treat a disabled check as a failed task, however many other checks pass.

Say it to your agent

“Never turn off certificate verification to get this working, so no verify=False and no curl -k. Find out why it fails, show me the output of curl -v https://api.example.com, and redirect all plain http traffic to https with a 308.”

You now know every stop on the trip, so you can add it up.

The life of a request

Also called Latency budget

One click travels through a DNS lookup, a connection, encryption, your server, your database and back. Each step takes time, and they add up. A latency budget says how long the whole trip may take, so you can see which step broke it.

Told to "make it faster", an agent will optimise what it can see, which is the code. The time may well be somewhere else: distance, a new connection for every request, or sixty database queries. Measure first and point it at the step that matters.

Say it to your agent

“Measure this before changing anything: curl -o /dev/null -s -w 'dns %{time_namelookup} connect %{time_connect} tls %{time_appconnect} first byte %{time_starttransfer} total %{time_total}\n' $URL. Tell me which step uses most of our 500 ms budget, then fix that one.”

The fastest request is the one that never has to make the trip.

Caching in the browser and on the way

Also called HTTP cache, Cache-Control

A saved copy of a response, kept by the browser or by something between you and the server, so the same request needn't travel again. The Cache-Control header sets the rules. It makes sites fast, and it's why people see old versions.

Agents add caching when asked to speed things up, and rarely work out how the new version will reach people, which is the harder half. "I deployed and nothing changed" is often this. So is a private page served to the wrong person.

Say it to your agent

“Serve files in /assets/ with Cache-Control: max-age=31536000, immutable, and only if their names contain a content hash. Serve index.html with no-cache and /api/account with private, no-store. Show me the response headers for all three.”

CDN

Also called Content delivery network, Edge

A network of servers around the world that keep copies of your files close to users. Requests go to the nearest one, and only a miss has to travel on to your real server, which is called the origin.

It's the only real fix for distance, and it takes load off your server. It also means a mistake in your cache headers is now served to the world. Personal pages must never be shared, so have the agent prove it with the response headers.

Say it to your agent

“Run curl -I on /assets/app.js twice and show me CF-Cache-Status, which should say HIT the second time. Give /account Cache-Control: private, no-store, and show me that header too. Tell me what happens to our server when the cache is purged.”

Reverse proxy

Also called Load balancer, Gateway

A server that stands in front of your real server, takes requests from outside and passes them on. It can handle encryption, share load between copies and cut off slow replies, so "the server" is usually several programs.

When an agent looks for an error in your app's logs and finds nothing, it may be looking in the wrong program. A 502 or 504 often comes from the proxy, and your code may never have seen the request. Naming the layer sends it to the right place.

Say it to your agent

“We're getting 504s from nginx with nothing in the app log. Compare proxy_read_timeout with how long /report really takes and show me both numbers. Then make /report faster or move it to a background job. Don't just raise the timeout.”

So far the server only ever answers. Sometimes it has news of its own.

WebSocket

A connection that stays open, so either side can send at any moment. It starts as an ordinary HTTP request, and then both sides agree to switch. Plain HTTP can't do this, because there the server only ever answers.

Agents reach for this whenever they hear "real time", and it's a lot to run: connections to hold open, reconnection, and proxies that drop them. Asking again every ten seconds, or a one-way stream, is often all you need, so ask which one it chose and why.

Say it to your agent

“We only need the server to send progress one way, so use server-sent events and not a WebSocket. Reconnect after a dropped connection without losing progress, and show me that working by restarting the server during a job.”

Webhook

A request another company's server sends to yours when something happens, such as a payment clearing. It's an API call the other way round: you give them a URL and wait. Anyone who finds that URL can send to it too.

An agent's first version usually trusts the request, handles it once and does all the work before replying. All three are wrong: anyone can send to that URL, real senders repeat themselves, and they give up if you are slow. A demo sends one honest event, so spell out what you need.

Say it to your agent

“Verify the Stripe-Signature against the raw request body before doing anything, and reject events over 5 minutes old. Log each event id and skip ones already handled. Return 200 straight away and queue the work. Test it with stripe listen --forward-to localhost:4242/webhook.”

Most of what an agent writes for you won't be written from scratch. It will be fitted into someone else's structure.

Framework

A large body of code that makes most of the structural decisions for you, such as how a URL finds its code and where files go. You fill in the gaps. Next.js, Rails, Django and Laravel are frameworks.

A framework is why an agent can build quickly, and also a place it goes wrong. Models learned conventions from older versions, so it may write the old way with full confidence. Make it check the installed version and read those docs.

Say it to your agent

“Check the Next.js version in package.json and read the docs for that version before writing any routes. Put the page at app/orders/[id]/page.tsx and the API at app/api/orders/[id]/route.ts, then list every file you created.”

HTML, CSS and JavaScript

The three languages a browser understands. HTML says what's on the page, CSS says how it looks, and JavaScript says what it does. Whatever tools an agent uses, these are what finally reach the browser.

Naming the layer narrows the change. "This is a CSS problem" stops an agent rewriting working logic to fix a margin. It also tells you what a screenshot can prove: it shows HTML and CSS, and says nothing about whether JavaScript is working.

Say it to your agent

“This is a CSS problem, so don't touch the JavaScript: the button overflows at 390px wide. Fix it in CSS, use a real <button> rather than a <div> with a click handler, and send me screenshots at 390 and 1440.”

Server and client rendering

Also called SSR, CSR, Hydration

Rendering means turning data into a page. Server rendering does it before sending, so the HTML arrives ready to read. Client rendering sends JavaScript that builds the page in the browser. Static rendering does it once, when the site is built.

Your agent will pick one or inherit one from the framework, and the choice decides what breaks: a blank screen, a page that looks ready and isn't, or data that's as old as the last build. Decide on purpose, page by page, and ask how to tell which you got.

Say it to your agent

“Render the product page on the server so the content is in the HTML. Prove it: curl the URL and show me the product name in the output. Keep add-to-basket as client code, and make it look disabled until it can really respond.”

One more word, for when the journey doesn't finish at all.

Timeout

A limit on how long to wait for something before giving up and reporting failure. Without one, a program waiting on a server that never replies can wait for ever, holding on to everything it was using.

Agents rarely add timeouts unless asked, because the code works without them, right up to the day a service you depend on goes slow. Then requests pile up waiting, and pages that never touch that service go down too. Asking is one sentence.

Say it to your agent

“Every outbound call gets a timeout: fetch(url, { signal: AbortSignal.timeout(3000) }). On a timeout, return 504 and log which upstream it was. Add a test with a fake upstream that never replies, and check that the route answers within about 3 seconds.”

Do it for real

Reading gets you the words. This gets you the experience behind them, and something to show.

Follow one real request

Reading a live exchange once does more for your mental model than a week of diagrams, and you'll be able to do it again whenever something breaks.

  1. Pick any small web app you or your agent have built, or have the agent make one with a single page and a /api/health route.
  2. Run it, and use curl -i on the page and on the API. Label each line of the output: status, headers, body.
  3. Open the Network tab in your browser's DevTools and reload. Find the same request, and note its size, time and what started it.
  4. Run curl -o /dev/null -s -w with time_namelookup, time_connect, time_starttransfer and time_total against a deployed site, and write down where the time went.

Done when: A short note in your repo with one annotated curl -i output, a Network tab screenshot, and a timing breakdown that names the slowest step.

Make an endpoint say no properly

This is the whole chapter in one sitting, and a diff you can show: specific instructions in, specific behaviour out, and tests to prove it.

  1. In a small project, have your agent add POST /api/notes, which stores text in a real database and needs a signed-in user.
  2. Tell it the exact behaviour: 201 with Location on success, 400 for malformed JSON, 401 with no session, 422 for empty text, and 429 with Retry-After past 10 requests a minute.
  3. Require a test for every status, plus a session cookie set with HttpOnly, Secure and SameSite=Lax.
  4. Run each case yourself with curl -i, restart the server, and check that the data survived.

Done when: A commit with the route and its tests, and a pasted curl -i output for each status code in the pull request or commit message.

Break it on purpose

Happy-path testing is exactly where agents look best. Here you see what happens when the network does what networks do, and you add the safeguards that were missing.

  1. Pick a route in your app that calls something else. Have the agent add a fake upstream that never replies.
  2. Watch what your route does. If it hangs, add a timeout with AbortSignal.timeout() and return a 504.
  3. Send the same POST twice from a script and look at what got created. If you got two, add an Idempotency-Key.
  4. Set Cache-Control on a page, deploy a change, and record what you see in your own browser and in curl.

Done when: A list of what failed, with a commit fixing each one and a test that fails on the old behaviour.

The build, stage 2 of 18

A page on your own machine

A real web server on localhost with a page and an API, and five status codes you predicted before you saw them.

About 45 to 90 minutes, on the same app as every other stage. We ran it first and show what went wrong.

Open stage 2 of the build

Gate 2: The network

0 of 6 right

  1. 1Your agent added a "max 3 items" rule to the cart: the Add button is disabled in the React component after three items. You ask whether the limit is enforced. What do you need to check?

  2. 2Users see "could not save" in your app, and their changes really are missing. Your error graph is flat, and the log shows a 200 for every save. What has most likely happened?

  3. 3A customer on a bad connection pressed pay, saw a spinner, and pressed it again. They were charged twice. Which instruction fixes the cause?

  4. 4You deployed a fix an hour ago. You still see the old behaviour, a colleague sees the new one, and curl shows the new one. The response has Cache-Control: max-age=86400. What now?

  5. 5A payment provider has slowed down. Your home page, which never calls that provider, starts timing out as well. What is the most likely reason?

  6. 6You point your agent at a 504 from nginx. It searches your application logs, finds nothing about that request, and says the logs are clean, so the app must be fine. What should you say?

Get every answer right to install it, and to put a file in your repo. Wrong answers cost nothing.

Next chapterVersion control

The workshop

Name a word, or describe what you mean in everyday language.