GraphQL Pagination and Cursor-Based Pagination in Express.js

Pagination is an essential feature in APIs, particularly when dealing with large datasets. It allows users to retrieve data in smaller, manageable chunks instead of overwhelming the server or client with large responses. In this chapter, we’ll explore GraphQL pagination and cursor-based pagination techniques in Express.js, from basic to advanced concepts. We'll cover the A to Z of pagination, including implementation details, examples, and explanations.

Introduction to Pagination in APIs

Why Pagination?

When working with databases or APIs that return large datasets, sending all data at once can lead to performance issues, longer load times, and high memory usage. Pagination addresses these concerns by splitting the data into smaller, more manageable parts, allowing clients to request only the data they need.

Types of Pagination

  • Offset-Based Pagination: Uses limit and skip to return a subset of data. For example, limit=10&offset=20 returns the 10 items starting from the 20th.
  • Cursor-Based Pagination: Uses a unique identifier (the cursor) to point to a specific record in the dataset. This is more efficient for large datasets and ensures data consistency during pagination.

In this chapter, we will focus on Cursor-Based Pagination, which is highly recommended for GraphQL APIs.

Introduction to GraphQL Pagination

GraphQL Pagination Overview

In a GraphQL API, pagination allows you to retrieve a limited subset of data in a single query. This is typically done using connections and edges. The connection is a wrapper around a list of items, while the edge contains the node (item) itself and additional pagination information, such as cursors.

GraphQL uses two primary methods for pagination:

  • Offset-Based Pagination (less common in GraphQL)
  • Cursor-Based Pagination (recommended and efficient for large datasets)

Cursor-Based Pagination: Concept

How Does Cursor-Based Pagination Work?

In cursor-based pagination, each record in the dataset has a unique cursor that acts as a pointer. This cursor can be used to fetch the next or previous set of results in subsequent requests. Instead of using skip and limit like offset-based pagination, cursor-based pagination uses the before and after arguments along with the cursor to fetch the next or previous page of data.

Why Use Cursor-Based Pagination?

  • No Data Skipping: Cursor-based pagination ensures consistency when the dataset changes, as it always uses a unique cursor to fetch data.
  • Efficient: Especially for large datasets, cursor pagination minimizes the performance hit caused by large offsets.

Setting Up Express.js and GraphQL

Before diving into pagination, we need to set up a simple Express.js server with GraphQL. This setup includes creating a GraphQL schema and resolvers.

Installation

				
					npm install express graphql express-graphql

				
			

Basic Setup of Express.js with GraphQL

				
					const express = require("express");
const { graphqlHTTP } = require("express-graphql");
const { GraphQLSchema, GraphQLObjectType, GraphQLString, GraphQLList } = require("graphql");

const app = express();

// Define the GraphQL schema
const ItemType = new GraphQLObjectType({
  name: "Item",
  fields: {
    id: { type: GraphQLString },
    name: { type: GraphQLString },
  },
});

const QueryType = new GraphQLObjectType({
  name: "Query",
  fields: {
    items: {
      type: new GraphQLList(ItemType),
      resolve: () => [
        { id: "1", name: "Item 1" },
        { id: "2", name: "Item 2" },
        { id: "3", name: "Item 3" },
      ],
    },
  },
});

const schema = new GraphQLSchema({
  query: QueryType,
});

// Set up the Express server with GraphQL endpoint
app.use("/graphql", graphqlHTTP({
  schema,
  graphiql: true,
}));

app.listen(4000, () => {
  console.log("Server running on http://localhost:4000/graphql");
});

				
			

This basic setup defines a simple schema with an items query returning a list of items.

Implementing Cursor-Based Pagination in GraphQL

Cursor-Based Pagination Schema

We’ll now modify the schema to implement cursor-based pagination. We will introduce edges and pageInfo to the schema.

Schema Definition with Pagination:

				
					const { GraphQLSchema, GraphQLObjectType, GraphQLString, GraphQLList, GraphQLInt } = require("graphql");
const { connectionArgs, connectionDefinitions, connectionFromArraySlice } = require("graphql-relay");

const { ItemConnection } = connectionDefinitions({
  nodeType: ItemType,
});

const PageInfoType = new GraphQLObjectType({
  name: "PageInfo",
  fields: {
    hasNextPage: { type: GraphQLString },
    hasPreviousPage: { type: GraphQLString },
  },
});

const QueryType = new GraphQLObjectType({
  name: "Query",
  fields: {
    items: {
      type: ItemConnection,
      args: {
        ...connectionArgs, // pagination arguments (first, after, last, before)
      },
      resolve: (parent, args) => {
        const items = [
          { id: "1", name: "Item 1" },
          { id: "2", name: "Item 2" },
          { id: "3", name: "Item 3" },
          { id: "4", name: "Item 4" },
          { id: "5", name: "Item 5" },
        ];
        // Using connectionFromArraySlice to slice items and manage pagination
        return connectionFromArraySlice(items, args, {
          sliceStart: 0,
          arrayLength: items.length,
        });
      },
    },
  },
});

const schema = new GraphQLSchema({
  query: QueryType,
});

				
			

Explanation:

  • connectionArgs: These arguments include first, after, last, and before, which are used for pagination.
  • connectionFromArraySlice: This function handles slicing the data array based on the pagination arguments.

Client Query Example for Pagination

Here’s how a client would query the data with pagination:

				
					query {
  items(first: 2) {
    edges {
      node {
        id
        name
      }
    }
    pageInfo {
      hasNextPage
    }
  }
}

				
			
  • first: 2: Fetch the first 2 items.
  • edges: Contains the paginated results (nodes).
  • pageInfo: Contains metadata about the pagination (e.g., hasNextPage).

Handling Cursors

To handle cursors, you need to encode and decode them. Cursors are typically base64-encoded strings representing unique identifiers for records.

Cursor Example: Encoding and Decoding

				
					const encodeCursor = (itemId) => Buffer.from(itemId).toString("base64");
const decodeCursor = (cursor) => Buffer.from(cursor, "base64").toString("utf-8");

// Example usage:
const cursor = encodeCursor("3"); // Encoding the item ID '3'
console.log(decodeCursor(cursor)); // Decodes back to '3'

				
			

In the schema, you would pass these cursors as after and before arguments to fetch the next/previous page.

Advanced Pagination Considerations

Using Cursors for Sorting

In cursor-based pagination, cursors are tied to specific records, so when data is sorted by a field (e.g., name or date), the cursor reflects the sorting order. This ensures that pagination is consistent even as data changes.

Limitations of Cursor Pagination

  1. Stateful: Requires the client to remember the cursor.
  2. Complexity: Handling cursors and decoding can increase the complexity of the API.

Best Practices for Pagination

  • Use Cursor-Based Pagination for Large Datasets: It ensures consistency and scalability.
  • Provide Metadata: Always return pageInfo to help clients understand the pagination state.
  • Limit Data Returned: Always return a fixed set of fields in paginated queries to improve performance.
  • Handle Edge Cases: Consider cases where data changes between pages, and ensure the client can gracefully handle them.

In this chapter, we explored GraphQL pagination and cursor-based pagination in Express.js. We discussed how to set up basic and advanced pagination using GraphQL schema, how cursors work, and how to implement efficient pagination strategies for large datasets. With the examples and explanations provided, you should now have a solid understanding of how to implement GraphQL pagination in your Express.js applications. Happy coding !❤️

Table of Contents

Contact here

Copyright © 2025 Diginode

Made with ❤️ in India