The API should always return sensible HTTP status codes. API errors typically break down into 2 types: 400 series status codes for client issues & 500 series status codes for server issues. At a minimum, the API should standardize that all 400 series errors come with consumable JSON error representation.
I have been doing some extensive research into this and other REST paging related questions lately and thought it constructive to add some of my findings here. I'm expanding the question a bit to include thoughts on paging as well as the count as they are intimitely related.
The paging metadata is included in the response in the form of response headers. The big benefit of this approach is that the response payload itself is just the actual data requestor was asking for. Making processing the response easier for clients that are not interested in the paging information.
There are a bunch of (standard and custom) headers used in the wild to return paging related information, including the total count.
X-Total-Count: 234
This is used in some APIs I found in the wild. There are also NPM packages for adding support for this header to e.g. Loopback. Some articles recommend setting this header as well.
It is often used in combination with the Link
header, which is a pretty good solution for paging, but lacks the total count information.
Link: </TheBook/chapter2>;
rel="previous"; title*=UTF-8'de'letztes%20Kapitel,
</TheBook/chapter4>;
rel="next"; title*=UTF-8'de'n%c3%a4chstes%20Kapitel
I feel, from reading a lot on this subject, that the general consensus is to use the Link
header to provide paging links to clients using rel=next
, rel=previous
etc. The problem with this is that it lacks the information of how many total records there are, which is why many APIs combine this with the X-Total-Count
header.
Alternatively, some APIs and e.g. the JsonApi standard, use the Link
format, but add the information in a response envelope instead of to a header. This simplifies access to the metadata (and creates a place to add the total count information) at the expense of increasing complexity of accessing the actual data itself (by adding an envelope).
Content-Range: items 0-49/234
Promoted by a blog article named Range header, I choose you (for pagination)!. The author makes a strong case for using the Range
and Content-Range
headers for pagination. When we carefully read the RFC on these headers, we find that extending their meaning beyond ranges of bytes was actually anticipated by the RFC and is explicitly permitted. When used in the context of items
instead of bytes
, the Range header actually gives us a way to both request a certain range of items and indicate what range of the total result the response items relate to. This header also gives a great way to show the total count. And it is a true standard that mostly maps one-to-one to paging. It is also used in the wild.
Many APIs, including the one from our favorite Q&A website use an envelope, a wrapper around the data that is used to add meta information about the data. Also, OData and JsonApi standards both use a response envelope.
The big downside to this (imho) is that processing the response data becomes more complex as the actual data has to be found somewhere in the envelope. Also there are many different formats for that envelope and you have to use the right one. It is telling that the response envelopes from OData and JsonApi are wildly different, with OData mixing in metadata at multiple points in the response.
I think this has been covered enough in the other answers. I did not investigate this much because I agree with the comments that this is confusing as you now have multiple types of endpoints. I think it's nicest if every endpoint represents a (collection of) resource(s).
We don't only have to communicate the paging meta information related to the response, but also allow the client to request specific pages/ranges. It is interesting to also look at this aspect to end up with a coherent solution. Here too we can use headers (the Range
header seems very suitable), or other mechanisms such as query parameters. Some people advocate treating pages of results as separate resources, which may make sense in some use cases (e.g. /books/231/pages/52
. I ended up selecting a wild range of frequently used request parameters such as pagesize
, page[size]
and limit
etc in addition to supporting the Range
header (and as request parameter as well).
While the response to /API/users is paged and returns only 30, records, there's nothing preventing you from including in the response also the total number of records, and other relevant info, like the page size, the page number/offset, etc.
The StackOverflow API is a good example of that same design. Here's the documentation for the Users method - https://api.stackexchange.com/docs/users
I prefer using HTTP Headers for this kind of contextual information.
For the total number of elements, I use the X-total-count
header.
For links to next, previous page, etc. I use HTTP Link
header:
http://www.w3.org/wiki/LinkHeader
Github does it the same way: https://docs.github.com/en/rest/overview/resources-in-the-rest-api#pagination
In my opinion, it's cleaner since it can be used also when you return content that doesn't support hyperlinks (i.e binaries, pictures).
Franci Penov's answer is certainly the best way to go so you always return items along with all additional metadata about your entities being requested. That's the way it should be done.
but sometimes returning all data doesn't make sense, because you may not need them at all. Maybe all you need is that metadata about your requested resource. Like total count or number of pages or something else. In such case you can always have URL query tell your service not to return items but rather just metadata like:
/api/members?metaonly=true
/api/members?includeitems=0
or something similar...
You could return the count as a custom HTTP header in response to a HEAD request. This way, if a client only wants the count, you don't need to return the actual list, and there's no need for an additional URL.
(Or, if you're in a controlled environment from endpoint to endpoint, you could use a custom HTTP verb such as COUNT.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With