We need a better convention for returning API results from collection endpoints.
Current behavior
Currently, when we return a collection of resources in an API, we use the default formatting provided by DRF, which returns a direct JSON list of results. For example:
[
{"id": 1, "item": "First item", "created": "2015-11-19T09:28:44Z"},
{"id": 2, "item": "Second item", "created": "2015-11-19T09:29:03Z"},
{"id": 3, "item": "Third item", "created": "2015-11-19T09:31:30Z"},
{"id": 4, "item": "Fourth item", "created": "2015-11-19T09:35:12Z"}
]
If we then paginate the results, using DRF's default pagination the above gets converted a JSON object that contains "previous" and "next" elements, which contain links to the previous and next pages respectively, a "count" element that contains the total number of results on all pages in the query, and a "results" element that contains the current page's results. If the above example were paginated with three elements per page, the response would look like:
{
"previous": null,
"next": "/path/to/results?page=2",
"count": 4,
"results": [
{"id": 1, "item": "First item", "created": "2015-11-19T09:28:44Z"},
{"id": 2, "item": "Second item", "created": "2015-11-19T09:29:03Z"},
{"id": 3, "item": "Third item", "created": "2015-11-19T09:31:30Z"}
]
}
Issues with current behavior
This is unsatisfactory for a few reasons:
- The results are in a different location depending on whether the query is paginated or not, which makes it more difficult to create a client to consume our data.
- The top level object controls pagination, which foregrounds meta-information while backgrounding the actual results the user was looking for.
- It also doesn't play nicely with other metadata we might want to add, as we would either have to create new subsections for other kinds of metadata, which are then at a different level than the pagination, or dump it all into the top-level, which creates a mess of un-namespaced metadata.
Proposed behavior
I propose that we update our API conventions for collection endpoints to:
Always return a JSON object, with an element "results", that contains a list of result items, whether or not the result set is paginated.
- If a result set is paginated, encapsulate pagination information under a top-level "pagination" entry alongside results.
This puts the results in a consistent location within the response, and provides a convenient namespace for the pagination metadata. Any given result can be tested for pagination very easily: If the response object contains a "pagination" element, the results are paginated.
Examples
Unpaginated:
{
"pagination": {
"count": 4,
"previous": null,
"next": "/path/to/results?page=2"
},
"results": [
{"id": 1, "item": "First item", "created": "2015-11-19T09:28:44Z"},
{"id": 2, "item": "Second item", "created": "2015-11-19T09:29:03Z"},
{"id": 3, "item": "Third item", "created": "2015-11-19T09:31:30Z"}
]
}
Paginated:
{
"pagination": {
"count": 4,
"previous": null,
"next": "/path/to/results?page=2"
},
"results": [
{"id": 1, "item": "First item", "created": "2015-11-19T09:28:44Z"},
{"id": 2, "item": "Second item", "created": "2015-11-19T09:29:03Z"},
{"id": 3, "item": "Third item", "created": "2015-11-19T09:31:30Z"}
]
}