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
.
type Query { events(input: EventsInput!): EventsResponse!}
Query input
The input would consist of two variables:
first
- how many records to limit the query to, andcursor
- 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.
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.
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.
// Resolverasync 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.
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.
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.