InteractiveFrameworks

Resources and methods

A URL names a thing and a method says what to do to it. Build the full set of cat routes, and see why PUT and DELETE can be repeated safely while POST cannot.

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

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:

URLWhat it names
/catsthe collection, every cat
/cats/3one item of it
/owners/1/catsa 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

MethodOn a collectionOn an itemSafeIdempotent
GETlist itread ityesyes
POSTadd to it; the server picks the idrarely usednono
PUTrarely usedstore this whole thing herenoyes
PATCHrarely usedchange these fieldsnonot guaranteed
DELETErarely usedremove itnoyes

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 OK with the resource, for a successful read, replace or change;
  • 201 Created for a new resource, with a Location header naming its URL;
  • 204 No Content for a delete: done, and nothing to say. In Hono, c.body(null, 204);
  • 404 Not Found when the item does not exist.

Your task

The app lists the cats and reads one. Add the rest, on the same cats array:

  1. POST /cats creates a cat from the JSON body under the next free id, one more than the highest id in use, and answers 201 with the cat and Location: /cats/<id>.
  2. PUT /cats/:id stores 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 (201 with a Location).
  3. PATCH /cats/:id changes only the fields the body carries and answers the cat, or 404 with { "error": "Cat <id> not found" }.
  4. DELETE /cats/:id removes the cat and answers 204 with no body, or the same 404.

When it fails

  • The "next free id" check fails with a duplicate id: the id was cats.length + 1. After a delete, or after a PUT chose id 7, the length no longer tracks the ids. Use the highest id in use plus one.
  • "Replace Tom" still shows grey: PUT merged into the old cat instead of replacing it. Build a new object from the id and the body.
  • "Patch Luna" lost her colour: PATCH replaced the cat. Merge the body into the stored one.
  • The first delete answers 200 with null or {}: 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.
  • GET is safe; GET, PUT and DELETE are idempotent and can be retried; POST is neither.
  • PUT sends the whole resource and may create it at that URL; PATCH sends only changes.
  • A new resource is 201 with a Location; a delete is 204 with 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.