API foundations
Design the Books API
Turn a small product requirement into resources, representations, routes, and a stable HTTP contract before writing handlers.
An API is an interface between programs. A browser, a mobile app or another server talks to your server, and they all agree on a set of URLs, methods and JSON shapes. That agreement is the contract.
In this course we build a Books API. Clients can list books, create one, read one, update one and delete one. Small enough to finish, big enough to hit every real problem: validation, errors, a database, auth, tests, deployment. Before writing a single handler, I want to design the contract on paper.
Nouns first, then methods
The REST style, which I covered in An introduction to REST APIs, models things as resources. A resource is a noun with a URL. Our nouns are the collection of books, and one single book:
/books
/books/:id
The actions come from HTTP methods, not from the URL. We never create /books/create or /deleteBook. GET reads, POST creates, PUT replaces, DELETE removes.
One representation
Decide what a book looks like on the wire. Write one example and stick to it everywhere:
{
"id": "1",
"title": "Dune",
"author": "Frank Herbert",
"publishedYear": 1965
}
Now split the fields in two groups. The client may send title, author and publishedYear. The server owns id and, later, createdAt. A client never picks an ID.
Also decide what an update means. Does PUT replace the whole book, or change only the fields it receives? For this project, PUT replaces. Partial updates would need PATCH, and we are not adding it.
The behavior table
This is the part most people skip, and it’s the most useful. One row per operation:
| Method and path | Input | Success | Body | Errors |
|---|---|---|---|---|
GET /books | optional filters | 200 | { books: [...] } | 400 |
POST /books | book fields | 201 | { book } | 400, 422 |
GET /books/:id | none | 200 | { book } | 404 |
PUT /books/:id | book fields | 200 | { book } | 404, 422 |
DELETE /books/:id | none | 204 | empty | 404 |
Notice the envelope { books: [...] } for the collection. An empty collection is still 200 with { books: [] }, not a 404. The shape never depends on how many books exist.
This table becomes our test list in the last module, and our OpenAPI document in the fourth. Every handler decision should trace back to one row here. If it can’t, the contract is not finished yet.
Before moving on, sketch the five routes, the book representation, and the error body for an unknown book in a text file inside the project. We’ll keep it updated as the API grows.
Lesson completed