Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing pagination in vanilla GraphQL

Every tutorial I have found thus far has achieved pagination in GraphQL via Apollo, Relay, or some other magic framework. I was hoping to find answers in similar asked questions here but they don't exist. I understand how to setup the queries but I'm unclear as to how I would implement the resolvers.

Could someone point me in the right direction? I am using mongoose/MongoDB and ES5, if that helps.

EDIT: It's worth noting that the official site for learning GraphQL doesn't have an entry on pagination if you choose to use graphql.js.

EDIT 2: I love that there are some people who vote to close questions before doing their research whereas others use their knowledge to help others. You can't stop progress, no matter how hard you try. (:

like image 380
NetOperator Wibby Avatar asked Feb 15 '18 22:02

NetOperator Wibby


People also ask

Is it possible to implement a pagination feature in Express Apollo server?

Pagination is the most common solution to this problem, and Apollo Client has built-in functionality that makes it quite easy to do. There are basically two ways of fetching paginated data: numbered pages, and cursors. There are also two ways for displaying paginated data: discrete pages, and infinite scrolling.

What is relay style pagination?

Relay-style pagination is an opinionated flavor of cursor-based pagination for GraphQL APIs. Relay itself is a JavaScript framework that can be used as a client to retrieve and cache data from a GraphQL API.

What is cursor pagination?

Cursor-based pagination works by returning a pointer to a specific item in the dataset. On subsequent requests, the server returns results after the given pointer.


2 Answers

Pagination in vanilla GraphQL

// Pagination argument type to represent offset and limit arguments
const PaginationArgType = new GraphQLInputObjectType({
  name: 'PaginationArg',
  fields: {
    offset: {
      type: GraphQLInt,
      description: "Skip n rows."
    },
    first: {
      type: GraphQLInt,
      description: "First n rows after the offset."
    },
  }
})

// Function to generate paginated list type for a GraphQLObjectType (for representing paginated response)
// Accepts a GraphQLObjectType as an argument and gives a paginated list type to represent paginated response.
const PaginatedListType = (ItemType) => new GraphQLObjectType({
  name: 'Paginated' + ItemType, // So that a new type name is generated for each item type, when we want paginated types for different types (eg. for Person, Book, etc.). Otherwise, GraphQL would complain saying that duplicate type is created when there are multiple paginated types.
  fields: {
    count: { type: GraphQLInt },
    items: { type: new GraphQLList(ItemType) }
  }
})

// Type for representing a single item. eg. Person
const PersonType = new GraphQLObjectType({
  name: 'Person',
  fields: {
    id: { type: new GraphQLNonNull(GraphQLID) },
    name: { type: GraphQLString },
  }
})

// Query type which accepts pagination arguments with resolve function
const PersonQueryTypes = {
  people: {
    type: PaginatedListType(PersonType),
    args: { 
      pagination: { 
        type: PaginationArgType, 
        defaultValue: { offset: 0, first: 10 } 
      },
    },
    resolve: (_, args) => {
      const { offset, first } = args.pagination
      // Call MongoDB/Mongoose functions to fetch data and count from database here.
      return {
        items: People.find().skip(offset).limit(first).exec()
        count: People.count()
      }
    },
  }
}

// Root query type
const QueryType = new GraphQLObjectType({
  name: 'QueryType',
  fields: {
    ...PersonQueryTypes,
  },
});

// GraphQL Schema
const Schema = new GraphQLSchema({
  query: QueryType
});

and when querying:

{
  people(pagination: {offset: 0, first: 10}) {
    items {
      id
      name
    }
    count
  }
}

Have created a launchpad here.

like image 162
Bless Avatar answered Sep 27 '22 18:09

Bless


There's a number of ways you could implement pagination, but here's two simple example resolvers that use Mongoose to get you started:

Simple pagination using limit and skip:

(obj, { pageSize = 10, page = 0 }) => {
  return Foo.find()
    .skip(page*pageSize)
    .limit(pageSize)
    .exec()
}

Using _id as a cursor:

(obj, { pageSize = 10, cursor }) => {
  const params = cursor ? {'_id': {'$gt': cursor}} : undefined
  return Foo.find(params).limit(pageSize).exec()
}
like image 32
Daniel Rearden Avatar answered Sep 27 '22 19:09

Daniel Rearden