Relay Pagination

One of the common ways of paginating in GraphQL is by using "Relay Pagnation". Relay Pagination is cursor-based and was made specifically with GraphQL in mind and is defined by the Relay Pagination Specification.

To make using relay pagination as possible graphql-mocks provides a Relay Wrapper and also exposes the underlying utility functions in case you want to paginate something yourself.

Relay Resolver Wrapper

The Relay Resolver Wrapper can paginate the results of the resolver it's wrapping, automatically. It assumes that what is returned from the wrapped resolver is the full result set and then it applies the pagination args, first, last, before, after, to return the current results.

When applied, the wrapper by default will paginate for any Connection fields that:

  • have pagination args (first, last, before, after)
  • return an object type that has an edges field

If it does not meet these conditions the wrapper returns the original result without paginating, unless the force option is passed.

import { relayWrapper } from 'graphql-mocks/relay';
relayWrapper({
// required, must specify a function that can return
// a cursor for a given node
cursorForNode: (node) => node.id
// defaults to false
force: false
})

The wrapper would take a single object or an array of object and return relay pagination for the connection field with the paginated objects as the node for each edge.

{
edges: [
{
node
cursor
}
],
pageInfo: {
hasNextPage,
hasPreviousPage,
startCursor,
endCursor
}
}

Example

This example uses an array of Actor objects:

[
{ id: '1', name: 'Suzy Bishop' },
{ id: '2', name: 'Eli Cash' },
{ id: '3', name: 'Margot Tenenbaum' },
];

This array will be paginated as an ActorConnection, using any pagination args, on the Query.actors field.

type Query {
actors(first: Int, last: Int, before: String, after: String): ActorConnection!
}
type ActorConnection {
edges: [ActorEdge!]!
pageInfo: PageInfo!
}
type ActorEdge {
node: Actor!
cursor: String!
}
type Actor {
id: ID!
name: String!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}

Using embed with the wrapper creates a Resolver Map Middleware that can be applied against all resolvers.

import { embed } from "graphql-mocks";
import { relayWrapper } from "graphql-mocks/relay";
const relayMiddleware = embed({
wrappers: [
relayWrapper({
cursorForNode: (node) => node.id,
force: false,
}),
],
});
  • The cursorForNode argument is required, representing a function that receives the node and must return an opaque cursor for pagination. In the example the id field on the node is returned for the cursor.
  • The relayWrapper will default with a force value of false.

Now, let's use the manufactured relayMiddleware middleware with our GraphQLHandler. Remember that this middleware must come after any other middlewares that would return the data being paginated.

import { GraphQLHandler } from "graphql-mocks";
import { graphqlSchema } from "./graphql-schema";
const handler = new GraphQLHandler({
resolverMap: {
Query: {
actors() {
return [
{ id: "1", name: "Suzy Bishop" },
{ id: "2", name: "Eli Cash" },
{ id: "3", name: "Margot Tenenbaum" },
];
},
},
},
middlewares: [relayMiddleware],
dependencies: { graphqlSchema },
});

The data being paginated could be from a previous middleware in the middlewares array but in this example the initial resolverMap provides the two characters that will be relay paginated based on the pagination args on the Query.actors fields.

Let's run a query against the handler selecting the first two actors:

const query = handler.query(`
{
actors(first: 2) {
edges {
node {
id
name
}
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
}
}
`);
query.then((result) => console.log(result));
Result:
{
  "data": {
    "actors": {
      "edges": [
        {
          "node": {
            "id": "1",
            "name": "Suzy Bishop"
          }
        },
        {
          "node": {
            "id": "2",
            "name": "Eli Cash"
          }
        }
      ],
      "pageInfo": {
        "endCursor": "2",
        "hasNextPage": true,
        "hasPreviousPage": false,
        "startCursor": "1"
      }
    }
  }
}

Notice that hasPreviousPage is true since we only requested the first two actors. Also, that the cursors are using the object's id based on the cursorForNode function specified.

Relay Utility functions

When relay pagination is needed and the Relay Wrapper isn't appropriate, the underlying utility functions can help.

paginateNodes

paginateNodes takes in an array of nodes and returns a paginated result for a connection field.

paginateNodes(
// collection of nodes, or objects, to paginate
nodes,
// from the resolver function
args: { first, last, before, after },
// a function that returns a cursor for a given node
cursorForNode,
)

Returning the following structure

{
edges: [
{
node
cursor
}
],
pageInfo: {
hasNextPage,
hasPreviousPage,
startCursor,
endCursor
}
}

isRelayConnectionField

Useful for determining if a GraphQLField is a relay connection field.