Pagination
Recall the problem chapter 7 solved. CourseTracker needed the registrar’s course catalog, and typing it in by hand or connecting straight to the registrar’s database were both bad options: the registrar’s schema was not built for another team to depend on, a direct connection risked exposing more than CourseTracker needed, and a traffic spike could slow down the registrar’s own work. The fix was the Public Courses API: a public, read-only API for the registrar’s course catalog, with CourseTracker as its first consumer among many.
Recall the designer’s job, too. Once the decision was made to build a RESTful HTTP API, the work was to decide the resources, the endpoint for each one, what a request looks like, what a response looks like, and how the API performs under load and over time. Chapter 7 made the first four of these decisions: the resources (terms, subject areas, courses, offerings), the endpoints, the requests, and the responses, down to the status code for every way a request can succeed or fail.
One decision was left open on purpose: how the API performs under load and over time. Response size is the first of these to design for. No response may be larger than one megabyte.
Take the offerings endpoint. A request for /offerings with no query parameters returns every offering the catalog has. The registrar’s catalog goes back years. Suppose it holds these numbers:
| What | Count |
|---|---|
| Offerings, all terms | 60,000 |
| Bytes in one offering, as JSON | 300 |
| Bytes in the response | 18 MB |
That is eighteen times the limit. It also makes the client download and parse sixty thousand offerings just to show a screenful. The same is true of courses, at a smaller scale. So a response for a list has to stop somewhere, and the client has to be able to request the rest.
The request
A client says which part of the list it wants with two query parameters. page is which page, starting at 1. page_size is how many items on a page. Here is a request for the second page of offerings in Fall 2026, fifty at a time:
GET /api/offerings?term=fall-2026&page=2&page_size=50 HTTP/1.1
Host: registrar.university.edu
Accept: application/json
Both parameters have a default, so a client that sends neither still gets a page and not the whole list. I set the default page_size to 50 and the maximum to 200. A page_size above 200, or a page below 1, gets 400 Bad Request. The maximum is what keeps the response under the limit. Two hundred offerings at 300 bytes each is sixty kilobytes.
The response
The body was a list. Now the response must also include the page number, the page size, and the total count. A JSON list holds only its items, so we change the body to a JSON object. The object has one field for the list and one field for each of the three values:
HTTP/1.1 200 OK
Content-Type: application/json
{
"offerings": [
{ "offering_id": 8431, "term": "fall-2026", ... },
{ "offering_id": 8432, "term": "fall-2026", ... }
],
"page": 2,
"page_size": 50,
"total": 412
}
total is the number of offerings that match the request, not the number on this page. From total and page_size the client knows there are nine pages, and it knows when it has read the last one. A request for a page past the last one, such as page=10 here, gets 200 OK with an empty list. The request is fine and there are no offerings on that page.
Every list endpoint gets this shape, including terms and subject areas. Those two lists are short and will never reach a second page. They get the shape anyway, so that a client reads every list the same way.
Reading the whole list
The API returns each list in a consistent order so that successive pages cover successive items.
A client that wants every offering in a term requests page 1, then page 2, and keeps going until the page it gets back is empty or it has read total items. Each request is under the limit. The client does the work of putting the pages together, and it can stop early if it finds the offering it is looking for. CourseTracker never needs to do this. A student’s search returns a page, and the student picks from it.