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 pathInputSuccessBodyErrors
GET /booksoptional filters200{ books: [...] }400
POST /booksbook fields201{ book }400, 422
GET /books/:idnone200{ book }404
PUT /books/:idbook fields200{ book }404, 422
DELETE /books/:idnone204empty404

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