PAGINATION.md

June 11, 2020 ยท View on GitHub

Pagination

For cursor pagination, you need to define a connection for the model you are querying.

For example if you need to return the collection of all the User records in the app:

  field :users_connection, Types::CustomTypes::UserType.connection_type, null: false

  def users_connection
    User.all
  end

With this defined, the query would need to have the following structure:

query {
  usersConnection {
    edges {
      node {
        id
        firstName
      }
    }
  }
}

and the response will have this structure:

{
  "data": {
    "userConnection": {
      "edges": [
        {
          "node": {
            "id": "1",
            "firstName": "Myesha",
            "lastName": "Stoltenberg"
          }
        },
        {
          "node": {
            "id": "2",
            "firstName": "Oscar",
            "lastName": "Kozey"
          }
        }
      ]
    }
  }
}

With this set up we can request useful information for pagination

at the connection level:

  pageInfo {

    startCursor: the cursor of the first returned value

    endCursor: the cursor of the last returned value

    hasPreviousPage: a boolean indicating if there are records before the first cursor

    hasNextPage: a boolean indicating if there are records after the last cursor
  }

  totalCount: number of values that the collection contains

in the node level:

  cursor: the cursor of the actual node

Adding this to the request would look something like:

query {
  usersConnection{
    pageInfo {
      startCursor
      endCursor
      hasPreviousPage
      hasNextPage
    }
    edges {
      cursor
      node {
        id
        firstName
        lastName
      }
    }
    totalCount
  }
}

with this response:

{
  "data": {
    "usersConnection": {
      "pageInfo": {
        "startCursor": "MQ",
        "endCursor": "Mw",
        "hasPreviousPage": false,
        "hasNextPage": true
      },
      "totalCount": 21,
      "edges": [
        {
          "node": {
            "id": "1",
            "firstName": "Myesha",
            "lastName": "Stoltenberg"
          },
          "cursor": "MQ"
        },
        {
          "node": {
            "id": "2",
            ...
      ]
    }
  }
}

Finally, the connection could receive some values as parameters to customize de response for only returning the desirable records.

first: int returns the first given number of records last: int returns the last given number of records after: String returns the records after the given cursor before: String returns the records before the given cursor

For example: usersConnection(first: 5 after: "Mw"){ ... } returns the first 5 records after the node with the "Mw" cursor.

It's important to take into account that hasPreviousPage would always be false unless you are paginating forward (UserConnection(first: 5)), and hasNextPage would always be false unless paginating backward (UserConnection(last: 5)). This is not an implementation error, it's due to how connections are designed.

If you need the hasPreviosPage value when paginating forward, you could make an additional query requesting the last 0 values before the node of which we want to know if there are values before:

query {
  userConnection(last: 0, before: "MQ") {
    pageInfo {
      hasPreviousPage
    }
  }
}

or the first 0 values after the desired node when we want to know if it hasNextPage while paginating backward.