Does anybody have a good guide for setting up cursor-based pagination, as recommended in The GraphQL docs, using Absinthe?
I don’t have a guide but I recently made a demo project that had cursor based pagination using Relay. Absinthe Relay image upload with Expo React Native
It’s possible to add Relay support for Absinthe, and as mentionned by @slouchpie it support cursor based pagination.
Hmm. While it’s encouraging that absinthe_relay | Hex is published by @benwilson512, and I see that the Relay project hosts the formal specification for this style of pagination, we don’t have Relay consumers in mind for our API; the users will be internal to a company and using a variety of programming languages.
I’ll have to think about whether adding Relay support via absinthe_relay
as the Absinthe docs show is the best way to enable pagination.
Neither do we. The Relay spec is still pretty useful though, both for pagination and for its node
pattern. Using it however is of course optional.
Thanks!
Another question: all the examples I see are about paginating associations - eg, the locations of a business. Do people also use this pattern for paginating top-level records, like “list all businesses”?
Oh, I think they do. I see this example in a blog post (using JavaScript).
{
news {
totalCount
edges {
node {
body
title
}
cursor
}
pageInfo {
startCursor
hasNextPage
}
}
}
```
This is exactly what I do in the demo project I linked above. I have a “pagination fragment” on the “RootQueryType”.
We use this for cursor pagination for our GraphQL API. Not sure if it’s what you’re after though.
I’m looking at absinthe_relay
and I see functions like cursor_to_offset/1 and offset_to_cursor/1 and from_query/4 referring to pagination using LIMIT
+ OFFSET
queries.
I’m confused because I thought the main purpose of using cursors was to avoid using OFFSET
for pagination due to its poor performance with later pages in large record sets. I was hoping to use a cursor to encode something like “the next page starts after id 200”. Is that approach compatible with absinthe_relay
?
I think I’m figuring this out. I’ll post a code snippet if I can get it working properly.
Yes, but you’ll want to use the from_slice
function. Basically, Absinthe.Relay from_query
does a dump simple limit + offset
in order to be able to work on everyone’s ecto config without any prior knowledge. If a user has a better way to interpret cursors in light of their particular database structures then their best option is to make the regular Ecto query and use from_slice
.
Do you have an example of how you are using the library? I’m trying to learn how to build a GraphQL API in elixir+phoenix, and I can’t wrap my head around how to do the pagination well.
@nathanl not a guide per-se but some examples from other open source Elixir codebases that may help you:
-
With Absinthe Relay
-
Without Absinthe Relay
I created a library (only on GitHub for now) to support keyset pagination with absinthe_relay
. It’s basically a drop-in replacement in your resolver. An example of paginating a usersConnection
:
# in your resolver function
AbsintheRelayKeysetConnection.from_query(
ecto_users_query,
&MyRepo.all/1,
# these args would come from the API user
%{
sorts: [%{name: :desc}, %{id: :desc}],
first: 10,
after: "0QTwn5SRWyJNbyIsMjZd"
},
# you would hard-code this configuration
%{unique_column: :id}
)
The reason a unique_column
is specified here is to be a “tie breaker” to ensure a unique value for sorting and paginating. See the moduledocs for more details.
If using this, you might put (for example) under your usersConnection
:
arg(:sorts, list_of(:user_order_by))
You’d use user_order_by
to define the ways you allow sorting for that record type. (You might want to consider what index support you have or need.)
input_object :user_order_by do
@desc "By email"
field(:email, :sort_direction)
@desc "By first name"
field(:first_name, :sort_direction)
# etc
end
:sort_direction
is a simple enum
type:
enum :sort_direction do
@desc "Ascending (for example, 1,2,3 or a,b,c)."
value(:asc)
@desc "Descending (for example, 3,2,1 or c,b,a)."
value(:desc)
end
@benwilson512 I’d love to contribute this to absinthe_relay | Hex if you’re interested. If not, I might release it as a separate package.
Hey Nathan! That library is great! I’ve some changes I plan to send as a PR (behaviour for Cursor to allow custom cursor implementations, making and marking the default Cursor as “tamper-resistant”, rather than tamper-proof, using Ecto’s dynamic/2
to allow > 3). Still working on some things, including adding tests for a NaiveDateTime
sort, and I’m thinking of allowing the key for sorts
to be configurable, although this could also be changed in the resolvers before calling from_query/4
.
I’m also planning some tests on nullable columns, to see how it affects sorting (IIRC, == nil
isn’t allowed in Ecto, and is_nil/1
should be used instead). Maybe this will be outside the scope though, given that asc
and desc
don’t cover the cases for all possible sorts:
:asc
:asc_nulls_last
:asc_nulls_first
:desc
:desc_nulls_last
:desc_nulls_first
And I’m not sure if it makes sense to allow all these cases.
Thanks @jeroenvisser101! Nice PR - let’s continue talking there.
FYI - this library is now published at absinthe_relay_keyset_connection | Hex