Implementing Cursor-based Pagination For Every GraphQL API

Backends often return a massive amount of data, and intaking all data simultaneously causes more overhead and increases the response time. Pagination preserves the application's performance by receiving small pieces of data in subsequent requests until the entire dataset is received.

When Facebook publicly released GraphQL as a client-driven API-based query language, it quickly gained hype because it allowed front-end developers to modify their backends easily. Applications using GraphQL are more efficient and work promptly on slow networks, and therefore, it is quickly replacing the traditional API architectures like REST. Pagination is an essential concept in GraphQL, but there aren't many resources on pagination in GraphQL.

In this post, we'll compare the different ways to handle pagination in GraphQL and learn how to configure a REST directive to perform cursor-based pagination for every REST API using StepZen.

TL;DR: You can find the complete documentation on paginating GraphQL using StepZen here.

Comparing Different Methods of Pagination in GraphQL

Pagination in GraphQL is not different from pagination in REST APIs, although some types of pagination better fit GraphQL. Before discussing the preferred pagination method in GraphQL, let's look at different pagination types. Typically, APIs offer three methods for pagination. These are:

1. Offset Pagination

Offset pagination consists of two primary parameters: limit and offset. The limit indicates the maximum number of results to show. And the offset denotes the position in the list from where the pagination starts.

Suppose you have a list of 100 students. If you set the limit parameter to 10 and offset to 20, the database engine will count from the 20th student and display the following ten students in each iteration. For instance, the first iteration will show the students from 20 to 30, then from 30 to 40, etc.

Although offset pagination is the most straightforward, it has a significant drawback. When some items are added or deleted, offset pagination results in repeated or missing data values.

Pros:

  • Most common way to do pagination in general.
  • Relatively simple implementation.
  • Most SQL-based databases support the limit and the offset variables, so it's easier to map values.

Cons:

  • Prone to data inconsistencies (repeated or missing data).
  • It doesn't provide information about more pages, total pages, or retrieval of previous pages.

2. Page Number Pagination

As the name specifies, page number pagination returns a single page per request. You all have seen a "next page" option when working with tables; each sheet shows the same number of results or entries. Similarly, page number pagination returns the same number of results per request.

Let's take an example to understand how it works. Page number pagination uses the after parameter to indicate the starting point for pagination. For instance, if you have a list of 30 students and set the after and the limit parameters to 23 and 5, respectively, then the output list will show a list of 5 students from the 23rd to 28th entry.

Page number pagination is more reliable as the upcoming results start from the last fetched values.

Pros:

  • Easier to implement.
  • It doesn't require complex logical analysis.

Cons:

  • Data inconsistencies.

3. Cursor Pagination

The third kind of pagination is cursor-based pagination. However, it is most complicated but is adaptable to dynamic data. Therefore, it is the preferred way to do pagination in GraphQL.

This method includes a specific parameter for the cursor. A cursor is nothing but a reference point that shows the position of an item in the database. The data are represented as nodes and a cursor as an edge in the graphical representation.

A cursor is a base64 encoded number. The query written for cursor-based pagination returns an object representation instead of a list.

Pros:

  • The preferred way to do pagination in GraphQL.
  • Provides valuable data for UX uses.
  • Allows reverse pagination.
  • No issues in the case of dynamic data as pagination are done to a specific row.

Cons:

  • It doesn't allow random access.
  • Needs complicated queries.

How to Implement Pagination in GraphQL using StepZen

Now you know about all the possible pagination methods and the preferred way of doing pagination in GraphQL, which is cursor-based pagination. If your REST API supports offset or page number pagination, you can easily implement cursor-based pagination in GraphQL using StepZen.

The two essential parameters you need to specify for every type of pagination are type and setters. The type parameters define the pagination style you want to implement. It has three possible values: PAGE_NUMBER, OFFSET, and NEXT_CURSOR. Next comes the setters parameters, which need to be pointed towards a field that indicates how many results or pages the response will output. You can find the list of all parameters with different pagination styles in the documentation.

Let's look at some examples:

1. Implementing Cursor Pagination for Offset

The below snippet illustrates how to implement GraphQL cursor-based pagination using StepZen for REST APIs supporting offset pagination. Note that the parameter first is set to the number of required results. The second parameter, after, is set to the starting point of the pagination.

customers(
  first: Int! = 20 
  after: String! = ""
): CustomerConnection
  @rest(
    endpoint:"https://api.example.com/customers?limit=$first&offset=$after"
    pagination: {
        type: OFFSET
        setters: [{field:"total" path: "meta.total_count"}]
      }
    )

Since it's the first request, the after parameter is equal to an empty string. The first parameter is set to 20, which means that the first 20 results will be returned. On the second request, you can change the value for after to be equal to the value of first from the previous request. Every new request will be a multiple of the first parameter.

2. Implementing Page Number Pagination

Take a look at the following code to get a better understanding of implementing cursor-based pagination in GraphQL using StepZen, when your REST API is relying on page number pagination:

customers(
  first: Int! = 20 
  after: String! = ""
): CustomerConnection
  @rest(
    endpoint:"https://api.example.com/customers?page=$after&per_page=$first"
    pagination: {
        type: NEXT_CURSOR
        setters: [{field:"nextCursor" path: "meta.next"}]
      }
    )

The above example illustrates some important aspects. The after parameter is set to an empty string, which indicates that it is the first request. In contrast, the first parameter is set to 20, meaning that 20 results will be returned per page. Remember, the value of the first parameter remains the same for subsequent requests as well. The after parameter in the upcoming request will be according to the page number you want to be returned. For instance, it is 3 for the third and 4 for the fourth page.

3. Implementing Cursor Pagination

Implementing cursor-based pagination is very similar to the other two pagination methods. The only difference is that you need to specify the type parameter as NEXT_CURSOR, and change the setters parameter to point towards the field that indicates the next cursor.

customers(
  first: Int! = 20 
  after: String! = ""
): CustomerConnection
  @rest(
    endpoint:"https://api.example.com/customers?first=$first&after=$after"
    pagination: {
        type: NEXT_CURSOR
        setters: [{field:"nextCursor" path: "meta.next"}]
      }
    )

This, of course, only applies if your REST API already supports cursor-based pagination.

How to Query Cursor Pagination in GraphQL

In the previous section, you've learned how to implement cursor-based pagination for any REST API using StepZen. Cursor-based pagination is for example, used by Relay, and is also supported by the StepZen GraphQL API. Cursor-based pagination is the preferred way to do pagination in GraphQL, as it provides valuable data for UX uses. But it comes with the downside of being more complex to query, as we saw in the first section.

Let's look at the Connection type that is used by cursor-based pagination:

type Customer {
  activities: [Activity]
  addresses: [Address]
  contacts: Contacts
  description: String
  designation: String
}

type CustomerEdge {
  node: Customer
  cursor: String
}

type CustomerConnection {
  pageInfo: PageInfo!
  edges: [CustomerEdge]
}

The CustomerConnection type is the one that is returned by the customers query. It contains a pageInfo field, which is of type PageInfo. The PageInfo type contains the following fields:

query MyQuery {
  customers {
    pageInfo {
      endCursor
      hasNextPage
      hasPreviousPage
      startCursor
    }
  }
}

These fields inform you about the current page, and whether there are more pages to be fetched. The endCursor and startCursor fields are the cursors that you need to use in the next request. The hasNextPage and hasPreviousPage fields indicate whether there are more pages to be fetched.

The edges field contains a list of CustomerEdge objects. The CustomerEdge type contains a node field of type Customer. To get the information about the customer, you would need to use the node field:

query MyQuery {
  customers(first: 3, after: "eyJjIjoiTzpRdWVyeTpwYXJrcyIsIm8iOjl9") {
    edges {
      node {
        id
        description
      }
    }
  }
}

The concept of "edges and nodes" is a bit confusing but very straightforward. It comes from the concept that everything in GraphQL is a graph. The edges field contains a list of CustomerEdge objects, information that is specific for this connection but not shared by all nodes. The CustomerEdge type contains a node field of type Customer. This is the actual customer information.

Connections, Edges, and Nodes in GraphQL

For Relay, you can find more information on GraphQL connections in the documentation.

Conclusion

GraphQL allows different kinds of pagination. But how does one decide which one will work best? GraphQL recommends using cursor-based pagination. However, it depends on the requirements of your application. If you are using a REST API already supporting cursor-based pagination, then it's a no-brainer. But if you are using a REST API that is not supporting cursor-based pagination, then you can use StepZen to implement it.

The common and the most straightforward kinds of pagination are offset and page number. Although they are relatively simple to implement, they are more suitable for static data. In contrast, cursor-based pagination is complex but best for changing data as it prevents data inconsistencies. With StepZen, it's straightforward to implement cursor-based pagination for any API.

Learn more by visiting StepZen Docs. Try it out with a free account, and we'd love to get your feedback and answer any questions on our Discord.