GraphQL Pagination

According to the relay connection spec, the PageInfo type should look like this:

type PageInfo {
  startCursor: String!
  endCursor: String!
  hasPreviousPage: Boolean!
  hasNextPage: Boolean!

I find it a well-thought-of spec, with consideration for constraints on the server's implementation:

The only gripe I have with it is that instead of the more cogent limit parameter when paginating, they use first when paginating forward and last when paginating backwards. I think limit could have worked just fine for both scenarios, with the direction of paging determined by specifying either before or after. Otherwise the spec is well thought of. One could page through an entire dataset in a consistent way.

Of course, reality isn't always accomodating. (Optimal) Implementations are not usually derived from "first principles" independent of requirements. Complex requirements beget complex implementation.

For example, this blog post at Slack uses the relay spec as a guideline, but with a few changes to accomodate their specific needs. Notably, they introduce a next_cursor field. I don't know how exactly they implement this, but I imagine it requires getting limit+1 rows and slicing off the last one to return as next_cursor. Not a big deal, but also not quite as elegant as the relay spec sets out to be. In any case, one has to do limit+1 to know if there is a next page.

Back to my own scenario, it appears the totalCount is sometimes required. The good news is that it's graphQL - you don't have to resolve the field if the client doesn't request it. The bad news is that the client could request it even when they don't strictly need it or even use it.

One alternative is to define a PageInfo type with totalCount and one without, and use the former only when explicitly requested and default to the one without. In any case, I'll keep it in mind the next time the topic of database performance comes up.

Date: 2022-04-07

Author: Brian Kamotho