192

I'm developing a REST API service for a large social networking website I'm involved in. So far, it's working great. I can issue GET, POST, PUT and DELETE requests to object URLs and affect my data. However, this data is paged (limited to 30 results at a time).

What would be the best RESTful way to get the total number of say, members, via my API?

Currently, I issue requests to a URL structure like the following:

  • /api/members - Returns a list of members (30 at a time as mentioned above)
  • /api/members/1 - Affects a single member, depending on request method used

My question is: how would I then use a similar URL structure to get the total number of members in my application? Obviously requesting just the id field (similar to Facebook's Graph API) and counting the results would be ineffective given only a slice of 30 results would only be returned.

Josh Correia
  • 3,807
  • 3
  • 33
  • 50
Martin Bean
  • 38,379
  • 25
  • 128
  • 201
  • 2
    possible duplicate of [Getting a count of returns seen by a RESTful request](http://stackoverflow.com/questions/1610862/getting-a-count-of-returns-seen-by-a-restful-request) – bzlm Sep 15 '10 at 08:55

15 Answers15

118

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.

Headers

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

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

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

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.

Envelope

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.

Separate endpoint

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).

Further thoughts

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).

Community
  • 1
  • 1
Stijn de Witt
  • 40,192
  • 13
  • 79
  • 80
  • I was particularly interested in `Range` header, however I couldn't find enough evidence that using anything apart from `bytes` as a range type, is valid. – VisioN Feb 13 '18 at 22:40
  • 3
    I think the clearest evidence can be found in [section 14.5 of the RFC](https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.5): `acceptable-ranges = 1#range-unit | "none"` I think this formulation explicitly leaves room for other range units than `bytes`, though the spec itself only defines `bytes`. – Stijn de Witt Feb 26 '18 at 14:49
  • Heed [this](https://stackoverflow.com/questions/3715981/what-s-the-best-restful-method-to-return-total-number-of-items-in-an-object#comment95881455_32417513) comment that you should **not** prefix non-standard headers with `X-`. – Leponzo Aug 30 '22 at 17:37
  • 1
    Yeah I totally disagree with it. HTTP headers are a mess, deal with it. No comment, not even from Tim Berners Lee, will solve it. You should use whatever you are comfortable with because it will not matter either way. Case in point: `X-Total-Count` is as close to a standard as it can get but does start with an `X`. You now deciding that you will leave out the `X` because some comment told you will actually add to the confusion, not lessen it. You cannot change that which has organically grown to what it is now. W3C gave tried and gave up. IANA is trying and will fail.give up. Just wait and see – Stijn de Witt Aug 31 '22 at 18:42
  • Funny and relevant comic: https://xkcd.com/927/ – Stijn de Witt Aug 31 '22 at 18:44
  • I wonder what would the recommendation be when the total number of elements is expensive to calculate. Adding it to every request would not be a good idea. – CLOVIS Nov 22 '22 at 10:46
102

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

Synchro
  • 35,538
  • 15
  • 81
  • 104
Franci Penov
  • 74,861
  • 18
  • 132
  • 169
  • Good idea. Every list returned should specify the total count as well. Otherwise, how would you know if there was a need to fetch the next page? – bzlm Sep 15 '10 at 08:53
  • 3
    +1: Definitely the most RESTful thing to do if fetch limits are going to be imposed at all. – Donal Fellows Sep 15 '10 at 09:06
  • 2
    @bzim You would know there is a next page to fetch because there is a link with rel="next". – Darrel Miller Sep 15 '10 at 11:00
  • @Darrel: Which in turn requires that the receiving code understand the word “next”. ;-) – Donal Fellows Sep 15 '10 at 12:21
  • @Franci Well it could work for pretty much an xml based media type. And it doesn't take a lot of imagination to figure out a way to encode links into JSON. Plenty of people are doing it. – Darrel Miller Sep 15 '10 at 23:47
  • 4
    @Donal the "next" rel is registered with IANA http://www.iana.org/assignments/link-relations/link-relations.txt – Darrel Miller Sep 15 '10 at 23:48
  • 3
    @Darrel - yes, it could be done with any type of "next" flag in the payload. I just feel that having the total count of the collection items in the response is valuable by itself and works as a "next" flag just the same. – Franci Penov Sep 16 '10 at 00:53
  • 7
    To return an object that is not a list of item is not a proper implementation of a REST API but REST does not provide any way to get partial list of results. So to respect that, I think we should use headers to transmit other informations like total, next page token and previous page token. I never tried it and I need advice from other developers. – Loenix Oct 24 '16 at 06:55
  • @Loenix Yes I agree with you. The cleanest solution would be to return an array of objects. So no [response envelope](https://stackoverflow.com/questions/9989135/when-in-my-rest-api-should-i-use-an-envelope-if-i-use-it-in-one-place-should-i). I like the [Range header](http://otac0n.com/blog/2012/11/21/range-header-i-choose-you.html) to communicate both current range and total number of items. Also, you can use the [Link header](https://apievangelist.com/2016/05/02/http-header-awareness-using-the-link-header-for-pagination/) for next, prev links. – Stijn de Witt Jun 26 '17 at 19:14
  • 1
    http://jsonapi.org/ is a good resource for a set of JSON REST specifications. It works similarly to StackOverflow's API and Facebook's API. In this case, you'd return a hash with 3 keys: `data` which is a list of users, `links` which is a hash of links used for pagination, and `meta` which is a hash that would contain your `total` count. – Aust Jul 12 '17 at 22:36
86

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).

martinsefcik
  • 346
  • 9
  • 20
Ondrej Bozek
  • 10,987
  • 7
  • 54
  • 70
  • 18
    [RFC6648](https://tools.ietf.org/html/rfc6648) deprecates the convention of prefixing the names of unstandardized parameters with the string `X-`. – JDawg Feb 05 '19 at 17:38
  • I agree with JDawg. Prefer "Resource-Count" or "Total-Count" or "MyApp-Total-Count" if you prefer in order to respect RFC6648 – fxrobin Dec 09 '20 at 12:59
  • where is it written that github is using x-total-count? I can only find information about link header. – The Fool Jan 02 '22 at 14:51
  • RFC6648 has just as little chance at changing the millions of servers that are already out there using e.g. `X-Total-Count` as W3C had getting billions of web pages to stop using `` tags. None at all. They first recommending to prefix an `X` and then recommending to not prefix is madness. It will not help either way. You can add any header you want. They are just strings. – Stijn de Witt Aug 31 '22 at 18:48
  • 1
    My advice would be to avoid inventing custom headers in the first place. Use what is out there. If people came up with `X-Total-Count` and clients come with built-in support for it, you 'respecting RFC6648' and using `Resource-Count` instead helps no one. Case in point: Google Search for e.g. "header rest total count" names `X-Total-Count` in **the title** of 6 of the 10 search results on that page but makes no mention of the alternatives suggested here. Crappy as it is, `X-Total-Count` is more of a standard than any other solutions mentioned on this page. Just use it. – Stijn de Witt Aug 31 '22 at 18:57
30

Alternative when you don't need actual items

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...

Community
  • 1
  • 1
Robert Koritnik
  • 103,639
  • 52
  • 277
  • 404
  • 20
    Embedding this information in headers has the advantage that you can make a HEAD request to just get the count. – felixfbecker Aug 29 '16 at 08:50
  • 2
    @felixfbecker exactly, thanks for reinventing the wheel and cluttering the APIs with all kinds of different mechanisms :) – EralpB Jan 10 '17 at 16:09
  • 3
    @EralpB Thanks for reinventing the wheel and cluttering the APIs!? HEAD is speced in HTTP. `metaonly` or `includeitems` is not. – felixfbecker Jan 11 '17 at 07:12
  • 3
    @felixfbecker only "exactly" was meant for you, the rest is for the OP. Sorry for the confusion. – EralpB Jan 11 '17 at 07:39
  • 1
    REST is all about leveraging HTTP and utilizing it for what it was intended for as much as possible. Content-Range (RFC7233) should be used in this case. Solutions within the body are no good, especially because it won't work with HEAD. creating new headers as suggested here are unnecessary and wrong. – Vance Shipley Oct 06 '18 at 15:04
24

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.)

bzlm
  • 9,626
  • 6
  • 65
  • 92
  • 4
    “Custom HTTP header”? That would come under the heading of being somewhat surprising, which in turn is contrary to what I think a RESTful API should be. Ultimately, it should be unsurprising. – Donal Fellows Sep 15 '10 at 09:05
  • 29
    @Donal I know. But all the good answers were already taken. :( – bzlm Sep 15 '10 at 09:07
  • 1
    I know too, but sometimes you've just got to let other people do the answering. Or make your contribution better in other ways, such as a detailed explanation of why it should be done the best way rather than others. – Donal Fellows Sep 15 '10 at 12:20
  • 4
    In a controlled environment, this could well be unsurprising, since it'd likely be used internally & based on your developers' API-policy. I'd say this was a good solution in some instances & worth having here as a note of a possible unusual solution. – James Billingham Nov 10 '13 at 22:24
  • 1
    I very much like using HTTP headers for this kind of thing (it's really where it belongs). The standard [Link header](http://www.w3.org/wiki/LinkHeader) might be appropriate in this case (the Github API uses this). – Mike Marcacci Aug 24 '14 at 17:45
  • @MikeMarcacci, can you give a specific example? What would the `rel` be to return the item count, and what would be on the resource whither the link points? – bzlm Aug 24 '14 at 19:10
  • Ah, yes – so my comment's not an exact answer to this question, but more like an endorsement of using headers for this kind of thing. While the "Link" header is specifically for pagination (and returns total pages instead of total results), I just wanted to add my vote to the idea that using a custom header wouldn't be totally unexpected. – Mike Marcacci Aug 24 '14 at 21:37
  • @MikeMarcacci I understand. It's just that your specific mention of the Link header (which IMHO isn't custom - which makes it an attractive choice) piqued my interest. We'll leave it as an exercise to the OP. :) – bzlm Aug 25 '14 at 10:47
  • 1
    I definitely think a HEAD request is the most correct option of the ones given. Implement a X-Total-Count which gets returns for both GET and HEAD verbs. When using GET it would be useful for paging. Seems like a win win to me. – arjabbar May 29 '15 at 21:11
  • this seems to be most valid answer so far, HEAD request is supposed to return no body but it can include count in headers. But HEAD request MAY be cached, that may cause some issue – Sushant Apr 30 '16 at 05:47
  • 1
    > *“Custom HTTP header”? [...] is contrary to what I think a RESTful API should be.* Interestingly, the custom http header (prefixed with X-) `X-Total-Count` has become the de facto 'standard'. :) – Stijn de Witt May 09 '17 at 13:13
  • 1
    @StijndeWitt That's why RFC6648 was introduced to stop the X- prefix on HTTP headers to avoid problems when headers are generally adopted and moved to the mainstream. The current recommendation is to *not* use X-prefixes (or similar, for other situations) in case an extension becomes a de facto standard. :) (I thought this was mentioned somewhere in this question, but apparently not.) – bzlm May 12 '17 at 17:28
  • 1
    @bzlm Yes I am aware of that RFC... On the other hand you get the situation where efforts to standardize a new header are hindered by existing systems using that header as a custom header... I think this is one of these things that just can't be done right. In the case of `X-Total-Count` it has become a sort of de-facto standard... You could of course choose to just add both headers... but then you are increasing the response overhead with redundant information. Choices, choices. :) – Stijn de Witt May 14 '17 at 19:41
15

I would recommend adding headers for the same, like:

HTTP/1.1 200

Pagination-Count: 100
Pagination-Page: 5
Pagination-Limit: 20
Content-Type: application/json

[
  {
    "id": 10,
    "name": "shirt",
    "color": "red",
    "price": "$23"
  },
  {
    "id": 11,
    "name": "shirt",
    "color": "blue",
    "price": "$25"
  }
]

For details refer to:

https://github.com/adnan-kamili/rest-api-response-format

For swagger file:

https://github.com/adnan-kamili/swagger-response-template

adnan kamili
  • 8,967
  • 7
  • 65
  • 125
14

As of "X-"-Prefix was deprecated. (see: https://www.rfc-editor.org/rfc/rfc6648)

We found the "Accept-Ranges" as being the best bet to map the pagination ranging: https://www.rfc-editor.org/rfc/rfc7233#section-2.3 As the "Range Units" may either be "bytes" or "token". Both do not represent a custom data type. (see: https://www.rfc-editor.org/rfc/rfc7233#section-4.2) Still, it is stated that

HTTP/1.1 implementations MAY ignore ranges specified using other units.

Which indicates: using custom Range Units is not against the protocol, but it MAY be ignored.

This way, we would have to set the Accept-Ranges to "members" or whatever ranged unit type, we'd expect. And in addition, also set the Content-Range to the current range. (see: https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.12)

Either way, I would stick to the recommendation of RFC7233 (https://www.rfc-editor.org/rfc/rfc7233#page-8) to send a 206 instead of 200:

If all of the preconditions are true, the server supports the Range
header field for the target resource, and the specified range(s) are
valid and satisfiable (as defined in Section 2.1), the server SHOULD
send a 206 (Partial Content) response with a payload containing one
or more partial representations that correspond to the satisfiable
ranges requested, as defined in Section 4.

So, as a result, we would have the following HTTP header fields:

For Partial Content:

206 Partial Content
Accept-Ranges: members
Content-Range: members 0-20/100

For full Content:

200 OK
Accept-Ranges: members
Content-Range: members 0-20/20
Community
  • 1
  • 1
Lepidopteron
  • 6,056
  • 5
  • 41
  • 53
5

What about a new end point > /api/members/count which just calls Members.Count() and returns the result

Steve Woods
  • 588
  • 4
  • 8
  • 33
    Giving the count an explicit endpoint makes it a standalone addressable resource. It will work, but will raise interesting questions for anybody new to your API - Is the count of the collection members a separate resource from the collection? Can I update it with a PUT request? Does it exist for an empty collection or only if there are items in it? If the `members` collection can be created by a POST request to `/api`, will `/api/members/count` be created as a side effect as well, or do I have to do an explicit POST request to create it before requesting it? :-) – Franci Penov Sep 15 '10 at 20:21
3

Seems easiest to just add a

GET
/api/members/count

and return the total count of members

willcodejavaforfood
  • 43,223
  • 17
  • 81
  • 111
  • 14
    Not a good idea. You obligate clients to make 2 request for building the pagination on their pages. First request to get the list of resources and second to count the total. – Jekis Dec 19 '13 at 08:10
  • I think it is good approach... you can also return just list of results as json and on client side check size of collection so such case is stupid example... moreover you can have /api/members/count and then /api/members?offset=10&limit=20 – Michał Ziobro Sep 02 '15 at 19:32
  • 3
    Also keep in mind that a lot of types of pagination do not require a count (Such as infinite scroll) - Why calculate this when the client may not need it – tofarr Apr 25 '19 at 22:02
  • 2
    it can also cause issues in some routers, because the `/api/members/:memberId` route pattern would match this too, so in reality you'd probably try to query the database for `where id = 'count'`. You can change the order of the routes or do other workarounds, or you make it work by specifying a regex for the `:memberId` route parameter, but it will likely be confusing anyways – David Namenyi Jan 27 '21 at 14:43
2

Sometimes frameworks (like $resource/AngularJS) require an array as a query result, and you can't really have a response like {count:10,items:[...]} in this case I store "count" in responseHeaders.

P. S. Actually you can do that with $resource/AngularJS, but it needs some tweaks.

  • What are those tweaks? They would be helpful on questions like this one: http://stackoverflow.com/questions/19140017/angujarjs-server-side-pagination-grand-total-items-using-resource – JBCP Feb 21 '14 at 19:18
  • Angular doesnt REQUIRE an array as query result, you just have to configure your resource with option object property : `isArray: false|true` – Rémi Becheras Dec 18 '15 at 10:22
2

You could consider counts as a resource. The URL would then be:

/api/counts/member
Frank Rem
  • 3,632
  • 2
  • 25
  • 37
2

Interesting discussion regarding Designing REST API for returning count of multiple objects: https://groups.google.com/g/api-craft/c/qbI2QRrpFew/m/h30DYnrqEwAJ?pli=1

As an API consumer, I would expect each count value to be represented either as a subresource to the countable resource (i.e. GET /tasks/count for a count of tasks), or as a field in a bigger aggregation of metadata related to the concerned resource (i.e. GET /tasks/metadata). By scoping related endpoints under the same parent resource (i.e. /tasks), the API becomes intuitive, and the purpose of an endpoint can (usually) be inferred from its path and HTTP method.

Additional thoughts:

  1. If each individual count is only useful in combination with other counts (for a statistics dashboard, for example), you could possibly expose a single endpoint which aggregates and returns all counts at once.
  2. If you have an existing endpoint for listing all resources (i.e. GET /tasks for listing all tasks), the count could be included in the response as metadata, either as HTTP headers or in the response body. Doing this will incur unnecessary load on the API, which might be negligible depending on your use case.
Kiryl Plyashkevich
  • 2,157
  • 19
  • 18
0

Seeing that "X-" prefix was deprecated. Here's what I came up with:

  1. Added another item count:23 to the response
  2. Removed the item from response before using data in the app.
Anon
  • 1,307
  • 2
  • 20
  • 30
0

If resource count is resource metadata => new endpoint

If the resource count (and possibly other metadata) is useful to the client app and end user, then it doesn't need to be in a header; it can legitimately be in the response body. There can be other metadata (other totals or averages or stats in general) that are relevant for a given resource.

In this case, I'd use a specific endpoint for resource metadata. Back to the social network members example in the original question, there could be:

GET /api/members         // collection of members, which could be paginated
GET /api/members/{id}    // a single member
GET /api/members/stats   // total member count; member count per region; average posts per member; etc.

Instead of stats you may prefer metadata, totals or something else in the endpoint (YMMV). But we treat this endpoint as returning something specific for the members resource. That's why it's a separate endpoint and not a query string parameter. Analogously, we should have a separate endpoint--not a query param--for the posts of a member:

GET /api/members/{id}/posts   

If resource count is for pagination => use headers

If the client app is handling the number of resources returned in the GET request for pagination purposes, then this resource count is messaging metadata.

In this case, I agree using headers is a better approach. You should look at the answers by Stijn de Witt and adnan kamili.

Paulo Merson
  • 13,270
  • 8
  • 79
  • 72
-1

When requesting paginated data, you know (by explicit page size parameter value or default page size value) the page size, so you know if you got all data in response or not. When there is less data in response than is a page size, then you got whole data. When a full page is returned, you have to ask again for another page.

I prefer have separate endpoint for count (or same endpoint with parameter countOnly). Because you could prepare end user for long/time consuming process by showing properly initiated progressbar.

If you want to return datasize in each response, there should be pageSize, offset mentionded as well. To be honest the best way is to repeat a request filters too. But the response became very complex. So, I prefer dedicated endpoint to return count.

<data>
  <originalRequest>
    <filter/>
    <filter/>
  </originalReqeust>
  <totalRecordCount/>
  <pageSize/>
  <offset/>
  <list>
     <item/>
     <item/>
  </list>
</data>

Couleage of mine, prefer a countOnly parameter to existing endpoint. So, when specified the response contains metadata only.

endpoint?filter=value

<data>
  <count/>
  <list>
    <item/>
    ...
  </list>
</data>

endpoint?filter=value&countOnly=true

<data>
  <count/>
  <!-- empty list -->
  <list/>
</data>
Wooff
  • 1,091
  • 12
  • 23