Pagination with Relay Connections

◕ GraphQLLesson 13Advanced

The Relay Cursor Connection Specification is the GraphQL community standard for pagination. It uses edges, nodes, and cursors to enable efficient, consistent pagination that works for both infinite scroll and page-based UIs.

1 Connection Schema Pattern
GraphQL SDL — Relay Connection Types
type PostConnection {
  edges:    [PostEdge!]!
  pageInfo: PageInfo!
  totalCount: Int!
}

type PostEdge {
  cursor: String!    # Opaque position marker
  node:   Post!      # The actual data
}

type PageInfo {
  hasNextPage:     Boolean!
  hasPreviousPage: Boolean!
  startCursor:     String
  endCursor:       String
}

type Query {
  # Forward pagination:  first + after
  # Backward pagination: last  + before
  posts(
    first:  Int
    after:  String
    last:   Int
    before: String
    filter: PostFilter
  ): PostConnection!
}
2 Connection Resolver Implementation
JavaScript — Cursor Pagination Resolver
function encodeCursor(id) { return Buffer.from("cursor:" + id).toString("base64"); }
function decodeCursor(cursor) { return Buffer.from(cursor, "base64").toString().split("cursor:")[1]; }

const resolvers = {
  Query: {
    posts: async (_, { first = 10, after, filter = {} }) => {
      const query = { ...filter };
      if (after) {
        const lastId = decodeCursor(after);
        query._id = { $gt: lastId };   // fetch records AFTER cursor
      }

      // Fetch one extra to determine hasNextPage
      const items = await Post.find(query).sort({ _id: 1 }).limit(first + 1);
      const hasNextPage = items.length > first;
      const edges = items.slice(0, first).map(post => ({
        cursor: encodeCursor(post.id),
        node: post
      }));

      return {
        edges,
        totalCount: await Post.countDocuments(filter),
        pageInfo: {
          hasNextPage,
          hasPreviousPage: !!after,
          startCursor: edges[0]?.cursor || null,
          endCursor:   edges[edges.length - 1]?.cursor || null
        }
      };
    }
  }
};
3 Code Challenge
Challenge: Build an infinite-scroll UI component in React using useQuery with fetchMore. When the user scrolls to the bottom, call fetchMore with the endCursor as the after variable, and merge the new results into the existing Apollo cache.