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:
hasPreviousPage and hasNextPage are optional
If the server can efficiently determine that elements exist prior to after, return true. If the server can efficiently determine that elements exist following before, return true. … else return false
- There is no requirement for the server to calculate the total number of pages. Counting is expensive on Postgres for example.
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.