0%

Request Bodies and a JSON API

Resources and Collections

Your server can now read a request body and check that it is labeled as such. Before writing the code that stores a book, decide where a new book goes, which request changes an existing one, and what each request answers. That decision is the shape of the API, and every client that uses it depends on it.

A URL names a thing

In Chapter 2 you met the idea that a names a resource and the says what to do with it. GET asks for it, and is safe: it changes nothing. PUT and DELETE are : sending one twice leaves the server in the same state as sending it once. POST is neither.

The reading has two kinds of resource:

  • The collection, /books: the list of every book.

  • An item, /books/7: one book, named by its id.

A write either goes to the collection, to add a book to it, or to one item, to change or remove that book. That gives four operations, often called CRUD: create, read, update, delete. Here is how this API maps them:

RequestEffectSuccessBody of the reply
POST /booksadds a book at an id the server picks201 Created, with location: /books/{id}the stored book
GET /booksnone200 OK{"books": [...]}
GET /books/{id}none200 OKthe book
PUT /books/{id}replaces that book with the body200 OKthe stored book
DELETE /books/{id}removes that book204 No Contentnone

Drawn as a shelf, the same table looks like this:

/books        the shelf         POST adds a book with a new id      -> 201
                                GET reads the whole shelf           -> 200
/books/7      one book on it    GET reads it                        -> 200
                                PUT replaces it                     -> 200
                                DELETE takes it off the shelf       -> 204

Any other method on either path still gets 405, with allow now listing what the table permits: GET, HEAD, POST on the collection and GET, HEAD, PUT, DELETE on an item. A PUT /books would replace the whole collection, and a DELETE /books would empty it; this API offers neither.

201 Created and 204 No Content are both 2xx successes, so a client that decides by class first, as yours does since Chapter 3, already treats them as success. Lesson 4 looks at what each one requires on the wire.

Who picks the id

For POST, the server picks. The client sends a title and an author to /books; the server gives the book the next free id and tells the client where it now lives, in the location header. The client does not know the id before it asks, which is why POST /books/7 makes no sense here.

For PUT, the URL picks. PUT /books/7 says “the book at /books/7 should now be this”, so the id comes from the path and never from the body: a body that says "id": 99 still replaces book 7.

This is also why the two methods behave differently when repeated. Two identical POST /books requests create two books with two ids. Two identical PUT /books/7 requests leave book 7 exactly as one did. Lesson 6 turns that difference into tests.

lets PUT create a resource at a URL that names nothing yet (RFC 9110, section 9.3.4). This API does not: ids are the server’s to hand out, so a PUT or DELETE to an id that holds no book gets 404.

Another API could answer a DELETE with 200 OK and the deleted book in the body, and both designs are valid HTTP. This one sends 204: the book is gone, and there is nothing to send. What matters is that the API says which it does, and SERVICE_CONTRACT.md does.

The same store, by hand

The state your handle receives is a of books keyed by id, plus next_id. Run this to see the three writes as dict operations, with the server’s choices printed:

Try it

The PUT ignored the body’s "id": 99, and deleting book 1 did not change next_id: the next POST gets 3, so a deleted id is never handed out again.

Which request adds a book whose id the server chooses?

What does this print?

books = {1: "Kindred", 2: "Dune"}
next_id = 3
books[next_id] = "Piranesi"
next_id += 1
books[2] = "Dune (revised)"
books.pop(1)
print(books, next_id)

In this API, what does a successful DELETE /books/7 send?

In the next lesson you write the three that make this table true.