This is a complimentary blog post to a video from the ShopifyDevs YouTube channel. In this article, Chuck Kosman, a Launch Engineer at Shopify Plus, dives deeper into the essential features you should know when working with GraphQL, focusing on GraphQL pagination. This is the fourth video in a five-part series of tutorials designed to improve your knowledge of the language. You’ll learn about the pageInfo
property, how to use the after
argument, and explore the advantages of working with cursor-based pagination.
Note: All images in this article are hyperlinked to the associated timestamps of the YouTube video, so you can click on them for more information.
Introducing a new product query
We’ll be picking up where we left off in part three of the tutorial where we looked at working with GraphQL fragments as a way to recycle fields. You can also step back and read up on what is GraphQL if you’d like an overview of getting started and the language itself.
Now, I have totally removed what I was working with in the last tutorial, and I've written a new query, which I called, using operation names, query ThreeProducts
. All this does is retrieve the first three products in the store. So, what I'm going to do here is I'll just press play and verify that this works.
We get these three apparel items in my test store.
You might be wondering, well, If I get the first three, how do I get anything beyond the first three? How do I start working with pages on GraphQL?
Just as a recap, If you read our article on getting started with GraphQL, you would know that when you request anything that's pluralized, Shopify's API is implementing a connection specification. Essentially, when you get anything plural, the type that you get back is a list of edges
that is called a connection.
"When you request anything that's pluralized, Shopify's API is implementing a connection specification."
Let’s look at that ProductConnection
.
Build for the world’s entrepreneurs
Want to check out the other videos in this series before they are posted on the blog? Subscribe to the ShopifyDevs YouTube channel. Get development inspiration, useful tips, and practical takeaways.
SubscribeSo, I got the list of edges
, that's why I had to write edges
and node
. Then at the end of node
is where I can actually get the title
and description
.
Introducing the PageInfo
property
There is another property here on the connections called PageInfo
, and it returns a PageInfo
type that is non-nullable.
Right at the same level as edges
, I'm going to get that field as well and let's actually inspect that type
to see what it tells me.
It's two booleans. One is called hasNextPage
and the other, hasPreviousPage
. So let's try this out. I'm going to press play again.
This is saying that hasNextPage
is true. So there is another page of results, and that means that there's at least one product that hasn't been captured in this particular page of results.
Now, because this is the first page, there's not a page before this one. Therefore, hasPreviousPage
is false.
So how do we actually get at that next page of results? What do I look to?
You might also like: How to Build a Shopify App in One Week.
Using relative cursor-based pagination
The way that Shopify has implemented GraphQL is relying on the connections specification that's part of the relay library. Many other GraphQL schemas have chosen to adopt cursor-based pagination over offset-based pagination. There are very good performance reasons behind the scenes for doing this.
"The way that Shopify has implemented GraphQL is relying on the connections specification that's part of the relay library."
For now, know that the way that cursor-based pagination works, is that each edge
essentially has an index to say this is a unique identifier of this particular edge
. Not a unique identifier of the node
, but the actual edge
itself, because connections return a list of edges
.
Let's jump to the schema definition of a product edge
to explore this a little further. In addition to the node
that I get back on that edge
, the other important property here is the string
.
So I'm actually going to say, tell me about the cursor
as well.
Now, I get a lot more data back in this array of edges
. Each edge
has this opaque, unique identifier of the cursor
.
The cursor
is kind of like the position of that edge
in the list.
Let’s say I needed to get the next three pages of results. What I would do is I'm going to look at this product's query and I'm going to take a look at the actual field
itself to see what arguments I can supply.
So I'm going to click on the actual field products
here, not the type
that gets returned, but products
, and these are lists of the arguments that I can do.
Now, I'm only using first
here, that's kind of the most basic one. But this goes along with another argument that I can use. What I'm going to say is, the last cursor
that I retrieved is this string
.
Employing the after
argument
So what we’re going to do, just for simplicity's sake, is we’re going to copy that string
and I'm going to supply a second argument to say after this particular cursor
.
Now, let's just take a look at the products here. I got back Ocean Blue Shirt
, Classic Varsity Top
, and Yellow Wool Jumper
.
If I’m issuing a request for the next page of results, I should expect that none of these show up. All my products are titled uniquely, so we’re saying, “Give me the first three products after this last cursor
.”
I'm going to press play.
The titles that I get back here are none that matched the last set of results (Striped Silk Blouse
, Floral White Top
, and Classic Leather Jacket
). And importantly, the page info has changed too.
Apparently there's still another set of results to be had here and there is now a previous page. So the page information is telling me that there is both a page after this, and a page before this.
You might also like: How to Use GraphQL Fragments.
I'm not sure how many products I have in the store, so this will actually be a good test.
Let's say I have ten products in the store and I change the argument to first:10
and I just get the first page of results. It looks like I have another page.
If I were to increase this to something like fifty products and I said first:50
, let’s see what happens when I press play.
Apparently there aren't actually 50 products in the store. It's either 50 or less. Now both hasNextPage
and hasPreviousPage
are False.
I've been using first and after, but if you wanted to go in reverse order, there's a couple of different things you could do. You could say you want the last elements from the list and you could say you want the last elements before some cursor
. You could also reverse the listing of these things so that instead of listing them in the order of created at ascending, you'd get them in order of created at descending. So you'd start with, say, the product that was created last.
You might also like: How to Use GraphQL Aliases.
Nested GraphQL pagination
Pagination is also nested.
Let's just go back to a smaller subset of results. I'll call it FirstThreeProducts
. I'm going to get rid of the pagination information at the top level of products. So, I'm going to get rid of cursor
and pageInfo
and let's say, on products
, I was also interested in their variants
and their title
.
So variants return a list, a connection type, which is a list of edges
.
For whatever reason, let's naively say that I want the first two variants
, but there's actually more than that. Let’s say I also wanted the edges
, node
, ID
of each variant
, and the title
of each Id
.
Now I can use those same pagination fields at the level of variance.
So next to edges
there was pageInfo
, and on the edge
itself was a property called cursor
. We also want to expand the information on pageInfo
for hasNext
and hasPrevious
.
Now I've got some nested pagination going on here, so I can do pagination at every level of lists that I have.
What this is saying is that I've requested the first two variant
s, but there is actually another page of variant
s for this product. For this next project down here, Classic Varsity Top
, that also has a nextPage
and so on and so forth.
I could use exactly the same principles per the schema to do things like after this particular cursor
.
Stay tuned for the final part of this tutorial where we'll be looking at a special argument that you can supply to any field that returns connections: the query
argument. If you missed it, you can start at the beginning of this series with GraphQL operation names and variables.
Build for the world’s entrepreneurs
Want to check out the other videos in this series before they are posted on the blog? Subscribe to the ShopifyDevs YouTube channel. Get development inspiration, useful tips, and practical takeaways.
SubscribeRead more
- How to Use Relative Pagination In Your Application
- How to Work with Shopify’s query Argument in GraphQL
- Shopify Fulfillment Orders API: A Better Fulfillment Experience
- How to Manipulate Images with the img_url Filter
- How to Customize Content by Country with Shopify
- Working with Product Variants When Building a Shopify Theme
- Shopify Tutorial: The product.liquid template
- Creating an Accessible Pagination with Liquid
- Three Types of Trending Apps in 2017 and What You Can Learn for 2018
- How to Optimize API Rate Limits