8

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. (:

NetOperator Wibby
  • 1,354
  • 5
  • 22
  • 44
  • Can you tell what you have tried and didn't work? Where did you get stuck? – Tal Z Feb 18 '18 at 22:30
  • I've tried pretty much any tutorial you can readily find online. I get stuck around the parts where the resolvers and/or edges come into play. – NetOperator Wibby Feb 19 '18 at 16:12
  • Can you give a more concrete example of something you are trying to achieve? – Tal Z Feb 19 '18 at 21:21
  • @TalZ I'm not sure how much more concrete I can be. How should I improve my question? I updated it with a link to https://www.howtographql.com. There, you will find tutorials on how to setup a GraphQL server. Not all of them have tutorials on implementing pagination. – NetOperator Wibby Feb 19 '18 at 22:47
  • Maybe you can post some code of a mongodb/mongoose query that uses pagination and a related resolver that you want to be able to use with that pagination. – Tal Z Feb 19 '18 at 22:51

2 Answers2

9

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.

Bless
  • 5,052
  • 2
  • 40
  • 44
3

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()
}
Daniel Rearden
  • 80,636
  • 11
  • 185
  • 183