Request Bodies and a JSON API
Resources and Collections
Your server can now read a request body and check that it is
A URL names a thing
In Chapter 2 you met the idea that a GET asks for it, and is safe: it changes nothing. PUT and DELETE are POST is neither.
The reading
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:
| Request | Effect | Success | Body of the reply |
|---|---|---|---|
POST /books | adds a book at an id the server picks | 201 Created, with location: /books/{id} | the stored book |
GET /books | none | 200 OK | {"books": [...]} |
GET /books/{id} | none | 200 OK | the book |
PUT /books/{id} | replaces that book with the body | 200 OK | the stored book |
DELETE /books/{id} | removes that book | 204 No Content | none |
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.
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 next_id. Run this to see the three writes as dict operations, with the server’s choices printed:
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