Files
What you'll learn
- Design URLs as resources, a collection and its items, with no verbs in the path
- Choose between POST, PUT, PATCH and DELETE by what each one means
- Explain which methods are safe and which are idempotent, and why a client may retry the idempotent ones
Resources and methods
An API that grows one route at a time ends up with paths like /getCats, /createCat, /deleteCatById and /updateCatAge, each with its own rules, and every client has to learn them one by one. HTTP already has a better answer, and it is the idea at the heart of what people call REST: a URL names a thing, and the method says what to do to it. Four paths collapse into one, /cats/3, and a client that knows HTTP already knows what GET, PUT and DELETE on it will do.
Resources are nouns
A resource is anything worth naming: a cat, the list of all cats, an owner's cats. Two shapes cover most of an API:
| URL | What it names |
|---|---|
/cats | the collection, every cat |
/cats/3 | one item of it |
/owners/1/cats | a collection that belongs to something |
Paths are plural nouns, and verbs stay out of them. /cats/3/delete is a path pretending to be a method.
Methods are verbs
| Method | On a collection | On an item | Safe | Idempotent |
|---|---|---|---|---|
GET | list it | read it | yes | yes |
POST | add to it; the server picks the id | rarely used | no | no |
PUT | rarely used | store this whole thing here | no | yes |
PATCH | rarely used | change these fields | no | not guaranteed |
DELETE | rarely used | remove it | no | yes |
Safe means the request changes nothing on the server: a crawler, a prefetch or a curious user can send it freely. Idempotent means sending it twice leaves the server in the same state as sending it once. DELETE /cats/3 twice: the cat is gone either way. The second answer is probably a 404 rather than a 204, and that is fine, because idempotence is about the server's state, not about identical responses.
POST is neither. Send POST /cats twice and there are two new cats. This is why it matters: networks fail in the middle, and a client that never heard back cannot tell whether its request arrived. An idempotent request can simply be sent again. A POST cannot, which is why payment APIs make clients send an idempotency key with every create.
PUT replaces, PATCH changes
The two are easy to confuse, and they mean different things. Take a note stored as { "id": 1, "text": "Feed Tom", "pinned": true }:
PUT /notes/1 {"text": "Feed Luna"}
→ {"id": 1, "text": "Feed Luna"} the body is the whole note now; "pinned" is gone
PATCH /notes/1 {"text": "Feed Luna"}
→ {"id": 1, "text": "Feed Luna", "pinned": true} only "text" changed
PUT also names its own URL, so it can create: PUT /notes/9 on a note that does not exist stores it at id 9 and answers 201 Created. That is what makes it idempotent: the second identical PUT finds the note it created and replaces it with the same thing. POST /notes cannot promise that, because the server chooses a new id each time.
Answers that fit
Lesson 3 is about status codes in full. This lesson needs four:
200 OKwith the resource, for a successful read, replace or change;201 Createdfor a new resource, with aLocationheader naming its URL;204 No Contentfor a delete: done, and nothing to say. In Hono,c.body(null, 204);404 Not Foundwhen the item does not exist.
Your task
The app lists the cats and reads one. Add the rest, on the same cats array:
POST /catscreates a cat from the JSON body under the next free id, one more than the highest id in use, and answers201with the cat andLocation: /cats/<id>.PUT /cats/:idstores the body as the whole cat at that id: it replaces an existing cat (200, and any field the body leaves out is gone) or creates one there (201with aLocation).PATCH /cats/:idchanges only the fields the body carries and answers the cat, or404with{ "error": "Cat <id> not found" }.DELETE /cats/:idremoves the cat and answers204with no body, or the same404.
When it fails
- The "next free id" check fails with a duplicate id: the id was
cats.length + 1. After a delete, or after aPUTchose id 7, the length no longer tracks the ids. Use the highest id in use plus one. - "Replace Tom" still shows
grey:PUTmerged into the old cat instead of replacing it. Build a new object from the id and the body. - "Patch Luna" lost her colour:
PATCHreplaced the cat. Merge the body into the stored one. - The first delete answers
200withnullor{}: a 204 has no body at all,c.body(null, 204).
Remember
- URLs are nouns, a collection and its items; the method is the verb.
GETis safe;GET,PUTandDELETEare idempotent and can be retried;POSTis neither.PUTsends the whole resource and may create it at that URL;PATCHsends only changes.- A new resource is
201with aLocation; a delete is204with no body.
Stuck? Show a hint
The next free id is Math.max(0, ...cats.map((x) => x.id)) + 1, which survives deletions and ids chosen by PUT. PUT builds a new object from the id and the body ({ id, ...body }) and puts it in the array's place, or pushes it; PATCH merges into the stored cat with Object.assign. c.body(null, 204) answers with no body.
Press Run tests to start the app. Its log appears here.Graded endpoints
The two cats the app starts with, colours included
POST adds to the collection and the server picks the id: 201, the stored cat, and a Location naming it
POST is not idempotent: the same request a second time creates a second cat
204 No Content: done, and nothing to say
DELETE is idempotent: the cat stays gone. The answer differs, the server's state does not
The item route agrees the cat is gone
PUT stores the body as the whole cat: Tom's colour was not sent, so it is gone
PATCH changes only what it carries: Luna's colour stays
PUT names the URL, so it can create there: 201 and a Location
PUT is idempotent: sending it twice leaves one Oliver, now replaced rather than created
The cat a PUT created is an item like any other
Id 4 was deleted and 7 was chosen by a PUT: the next id is one more than the highest in use, never a reused one
Every change above, and nothing else: one Milo, one Oliver, Tom without a colour
There is nothing to change