Why the method matters more than you think
In Level 1 you saw that every HTTP request starts with a method: GET /books/1 HTTP/1.1. The method looks like a label, but clients, proxies, browsers and retry libraries all read it as a promise about what the request will do.
That promise matters because networks fail. Say your app sends a request, the connection drops, and no response comes back. Did the server act on the request or not? You can't tell. Whether it's safe to send the request again depends on the method. Pick the wrong one and a retry can charge a customer twice.
The five everyday methods
Think of a library catalogue:
| Method | Meaning | Library analogy |
|---|---|---|
GET /books/1 |
Read a resource | Look up a catalogue card |
POST /books |
Create a new item in a collection (or run a server-defined action) | Hand the librarian a new book; they assign the shelf number |
PUT /books/1 |
Replace the resource at this URL with what I send | Swap the whole card for a new one |
PATCH /books/1 |
Change part of the resource | Correct one line on the card |
DELETE /books/1 |
Remove the resource | Throw the card away |
Notice who chooses the URL. With POST you send to a collection and the server picks the new item's id. With PUT you name the exact URL yourself, and the server makes that resource match what you sent.
Safe and idempotent: the two properties RFC 9110
RFC 9110 (HTTP Semantics) defines two properties.
Safe means the method is intended only to retrieve information and should not change server state. The safe methods are GET, HEAD, OPTIONS and TRACE.
Idempotent means making the same request once or many times has the same intended effect as making it once. The idempotent methods are PUT and DELETE, plus all the safe methods.
POST is neither. PATCH is defined in its own RFC (RFC 5789), which says plainly that PATCH is "neither safe nor idempotent". A patch such as "append this tag" or "add 1 to stock" gives a different result every time you repeat it.
| Method | Safe? | Idempotent? |
|---|---|---|
| GET, HEAD, OPTIONS | ✅ | ✅ |
| PUT | ❌ | ✅ |
| DELETE | ❌ | ✅ |
| POST | ❌ | ❌ |
| PATCH | ❌ | ❌ (not guaranteed) |
Gotcha: idempotent is about the effect, not the response. The first
DELETE /books/1removes the book. The second finds nothing to remove and may get a different response. The state is the same either way: the book is gone. That's what counts.
Gotcha: "safe" is a promise you keep. Nothing stops you writing
GET /books/1/delete. Crawlers, link prefetchers and caches assume GET is harmless, so sooner or later one of them will "click" it. Never change state on a GET.
This is why idempotency decides retries. RFC 9110 allows clients to retry idempotent requests automatically after a connection failure, because a repeat can't cause extra harm. A client should not retry a POST automatically unless it has some other way to know the first one didn't take effect. A common pattern is an Idempotency-Key header: the client sends a unique key with the POST and the server ignores repeats that carry the same key. That header is still an IETF draft, not a finished standard, but many payment APIs use the idea.
Hands-on: prove it with curl
We'll use json-server, a small Node tool that turns a JSON file into a REST API you can write to. You need Node.js installed.
# 1. Create a data file with one book
echo '{ "books": [ { "id": "1", "title": "Dune", "stock": 3 } ] }' > db.json
# 2. Start the API (serves http://localhost:3000 and saves changes back to db.json)
npx json-server db.json
In a second terminal:
Repeat a PUT three times
# Replace book 1 completely. Run this exact command three times.
curl -X PUT http://localhost:3000/books/1 \
-H "Content-Type: application/json" \
-d '{"title": "Dune (2nd ed.)", "stock": 5}'
{ "id": "1", "title": "Dune (2nd ed.)", "stock": 5 }
Check the collection:
curl http://localhost:3000/books
You still have one book with the same content. Three PUTs had the same effect as one.
Repeat a POST three times
# Create a new book. Run this exact command three times.
curl -X POST http://localhost:3000/books \
-H "Content-Type: application/json" \
-d '{"title": "Neuromancer", "stock": 2}'
Each response has a different auto-generated id. json-server's ids are short random strings, so yours will differ from these:
{ "id": "a3f9", "title": "Neuromancer", "stock": 2 }
curl http://localhost:3000/books # now 4 books: Dune + three Neuromancers
That's the duplicate-order bug in miniature. If a retry library had resent this POST after a timeout, you'd have the same mess.
PUT replaces, PATCH changes part
# PATCH sends only the field that changes; title is kept
curl -X PATCH http://localhost:3000/books/1 \
-H "Content-Type: application/json" -d '{"stock": 9}'
# PUT sends a full replacement. Leave out a field and expect it to be gone
curl -X PUT http://localhost:3000/books/1 \
-H "Content-Type: application/json" -d '{"stock": 1}'
curl http://localhost:3000/books/1 # title has disappeared
Pitfall: using PUT for partial updates is a classic bug. A client that sends only the changed fields with PUT quietly wipes everything else. Partial update? Use PATCH.
Check your results
- After the PUT runs:
GET /booksshows exactly one Dune. - After the POST runs: three Neuromancer entries with three different ids.
- After the last PUT: book 1 has
stockbut notitle. - Open
db.json: it matches what the API returns.
Key takeaways
- GET reads, POST creates in a collection (the server picks the id), PUT replaces at a URL you name, PATCH changes part of a resource, DELETE removes it.
- RFC 9110: safe = GET, HEAD, OPTIONS, TRACE. Idempotent = those plus PUT and DELETE.
- POST and PATCH are not idempotent, so don't retry them blindly. An idempotency key is the usual fix.
- Idempotency is about the effect on server state, not about getting an identical response.
- Never change state with GET. Crawlers, prefetchers and caches assume it's harmless.