Pagination In Prisma And GraphQL

Database

GraphQL

12/12/2022


Assume we are retrieving hundreds of events for a website and we do so by paginating the results.

This example describes the implementation details of cursor-based pagination on the server side.

Designing the GraphQL schema, we would have an events query that:

  • Accepts an input of type EventsInput, and
  • Returns an output of type EventsResponse.
GRAPHQL
type Query {
events(input: EventsInput!): EventsResponse!
}

Query input

The input would consist of two variables:

  • first - how many records to limit the query to, and
  • cursor - which record to start the query from (fundamental principle behind cursor-based pagination).

The cursor value is optional as it's not required to query the very first page of results.

GRAPHQL
input EventsInput {
first: Int!
cursor: ID
}

Query output

The output of the query would contain the queried results in the edges field. The pageInfo field contains info on whether there is another page/batch of events available as well as what the ID of the cursor (i.e. final record) of the current page is.

GRAPHQL
type EventsResponse {
edges: [Event!]!
pageInfo: PageInfo!
}
type PageInfo {
endCursor: ID
hasNextPage: Boolean!
}

Similarly, the cursor is nullable as we do not expect one in an empty events query.

Prisma

After defining the GraphQL schema of paginated event data, we need to take care of actually retrieving and returning the data. This happens in the events resolver.

The 1st step consists of querying all events using the pagination properties the resolver receives.

TYPESCRIPT
// Resolver
async function events(_parent, { input }, _context) {
const { first, cursor } = input;
const events = await prisma.event.findMany({
// Limit the number of events returned by this query.
take: first,
// Conditionally use a cursor if it exists.
...(cursor && {
skip: 1, // Do not include the cursor itself in the query result.
cursor: {
id: cursor,
}
}),
});

If no results are retrieved, it means we've reached the end of the pagination (... or perhaps there's no events in the database 😆) and we return early.

TYPESCRIPT
if (events.length === 0) {
return {
edges: [],
pageInfo: {
endCursor: null,
hasNextPage: false,
},
}
}

If there is data, the next step is to identify the cursor of that batch of results. Then we use said cursor to peak into the next page. If that query contains any events, it means that there is another page of results.

TYPESCRIPT
const newCursor = events[events.length - 1].id;
const nextPage = await prisma.event.findMany({
// Same as before, limit the number of events returned by this query.
take: first,
skip: 1, // Do not include the cursor itself in the query result.
cursor: {
id: newCursor,
},
});
return {
edges: events,
pageInfo: {
endCursor: newCursor,
hasNextPage: nextPage.length > 0,
},
};
}

Lastly, we return any information we retrieved (including the existence of a next page) in the GraphQL query.


WRITTEN BY

Code and stuff