SnapShooter Backups Server, Database, Application and Laravel Backups - Get fully protected with SnapShooter

Your first GraphQL API - Pagination

Pagination is a common technique for loading large data set. It provides good user experience without sacrificing server performance.

In this tutorial, we are going to implement pagination for our data table.

Creating more data for pagination

It may cause a performance issue if you have a huge amount of records in your database and you try to load them at once. The idea of pagination is to ease server from loading huge amount of data at once, by allowing users to navigate all your records page by page.

We need to create more records to demonstrate pagination properly in this tutorial. Open ArticlesRepository file and let's add some more records:

src/Repository/ArticlesRepository:

<?php
namespace StarTutorial\Repository;
 
class ArticlesRepository
{
 
    public static function findAll()
    {
        return [
            [
                'id' => 1,
                'title' => 'My First GraphQL API',
                'content' => 'It is all about GraphQL',
                'created' => new \DateTime('2017-02-24 11:20:10'),
                'author_id' => 1,
            ],
            [
                'id' => 2,
                'title' => 'GraphQL History',
                'content' => 'GraphQL is created by Facebook',
                'created' => new \DateTime('2017-03-27 12:20:10'),
                'author_id' => 2
            ],
            [
                'id' => 3,
                'title' => 'GraphQL vs Rest',
                'content' => 'GraphQL is a query language, whereas Rest is a protocol',
                'created' => new \DateTime('2017-04-30 13:20:10'),
                'author_id' => 2
            ],
            [
                'id' => 4,
                'title' => 'GraphQL vs Rest Round 1',
                'content' => 'GraphQL is wins 1st battle',
                'created' => new \DateTime('2017-05-01 13:20:10'),
                'author_id' => 2
            ],
            [
                'id' => 5,
                'title' => 'GraphQL vs Rest Round 2',
                'content' => 'GraphQL is wins 2nd battle',
                'created' => new \DateTime('2017-05-02 13:20:10'),
                'author_id' => 2
            ],
            [
                'id' => 6,
                'title' => 'GraphQL vs Rest Round 3',
                'content' => 'GraphQL is wins 3rd battle',
                'created' => new \DateTime('2017-05-03 13:20:10'),
                'author_id' => 2
            ],
            [
                'id' => 7,
                'title' => 'GraphQL vs Rest Round 4',
                'content' => 'GraphQL is wins 4th battle',
                'created' => new \DateTime('2017-05-04 13:20:10'),
                'author_id' => 2
            ],
            [
                'id' => 8,
                'title' => 'GraphQL vs Rest Round 5',
                'content' => 'GraphQL is wins 5th battle',
                'created' => new \DateTime('2017-05-05 13:20:10'),
                'author_id' => 2
            ],
            [
                'id' => 9,
                'title' => 'GraphQL vs Rest Round 6',
                'content' => 'GraphQL is wins 6th battle',
                'created' => new \DateTime('2017-05-06 13:20:10'),
                'author_id' => 2
            ],
            [
                'id' => 10,
                'title' => 'GraphQL vs Rest Round 7',
                'content' => 'GraphQL is wins 7th battle',
                'created' => new \DateTime('2017-05-07 13:20:10'),
                'author_id' => 2
            ]
        ];
    }
}

Understanding GraphQL pagination mechanism

There are a number of different ways of implementing pagination in general. Whereas in GraphQL, the best practice is using cursor based pagination.

There is a full page explaining what cursor based pagination is in GraphQL official page. We encourage that you take look at it and understand the basis of it. However do not worry about too much if you are not able to get the full understanding of it at first try, we will go through the process of implementation later on.

In short, cursor based pagination returns cursors along with data records as well as some page related information such as whether it has reached the end, has previous page, has next page and so on.

Implementing pagination

An imaginary pagination request would look like this:

{
  articlesConnection(first:2 after:"Y3Vyc29yMQ==") {
      edges {
        node {
          title
        }
        cursor
      }
      pageInfo {
        endCursor
        hasNextPage
      }
    }
}

As you might have noticed. Some parts of the request have some arbitrary names such as edges, node, cursor as well as pageInfo. They seem reusable and we can make some generic class for them.

It turns out that those pagination related classes are indeed created by someone else and make them into a generic package.

The package is called GraphQL Extensions and it provides a lot of useful utility classes for youshido/GraphQL.

Install GraphQL Extensions:

?

composer require youshido/graphql-extensions

The algorithm for implementing GraphQL pagination is explained at https://facebook.github.io/relay/graphql/connections.htm#sec-Pagination-algorithm. Basically two directions we can go when traversing with a cursor and they are forward and backwards. And the algorithm is around how we deal with the directions. Read the article and get a rough idea how the algorithm works, it is actually quite straightforward.

With a rough idea of the algorithm in mind, create a field class ArticlesConnections that will resolve the pagination request:

?

<?php
 
 
namespace StarTutorial\Field;
 
 
use StarTutorial\Repository\ArticlesRepository;
use StarTutorial\Type\ArticleType;
use Youshido\GraphQL\Config\Field\FieldConfig;
use Youshido\GraphQL\Execution\ResolveInfo;
use Youshido\GraphQL\Field\AbstractField;
use Youshido\GraphQL\Relay\Connection\ArrayConnection;
use Youshido\GraphQL\Relay\Connection\Connection;
use Youshido\GraphQLExtension\Type\CursorResultType;
 
class ArticlesConnection extends AbstractField
{
 
    public function getType()
    {
        return new CursorResultType(new ArticleType());
    }
 
    public function build(FieldConfig $config)
    {
        $this->addArguments(Connection::connectionArgs());
    }
 
    public function resolve($value, array $args, ResolveInfo $info)
    {
        return ArrayConnection::connectionFromArray(ArticlesRepository::findAll(), $args);
    }
}

We will run through each method from ArticlesConnection class:

  • getType(): It addresses the return type of the field, which is a cursor based pagination result. CursorResultType is the reusable class that does the actual work.
  • build(): Here yet we used another utility class that helps us set available arguments for pagination.
  • resolve(): You should know by now, this is the resolver method that returns results based on the request. Normally, this method will delegate to the other service class from business layer, and the service class will trigger database calls based on the algorithm mentioned above. However, because our fake repository class simply returns a static array, we can use another utility class ArrayConnection from youshido/GraphQL. The method looks at an array and filter results with request's arguments. It uses the base64 value of array index as the cursor value.

The last thing we need to do is to add ArticlesConnection to the schema. Meanwhile, we will create a simple request to our schema:

Create a new file tutorial-pagination.php:

<?php
 
use StarTutorial\Field\ArticleField;
use StarTutorial\Field\ArticlesConnection;
use Youshido\GraphQL\Execution\Processor;
use Youshido\GraphQL\Schema\Schema;
use Youshido\GraphQL\Type\Object\ObjectType;
 
require_once 'vendor/autoload.php';
 
$processor = new Processor(new Schema([
    'query' => new ObjectType([
        'name' => 'RootQueryType',
        'fields' => [
            new ArticleField(),
            new ArticlesConnection()
        ]
    ]),
]));
 
$processor->processPayload(
    '{ articlesConnection(first:2, after: "YXJyYXljb25uZWN0aW9uOjE="){edges { node { title } cursor }, pageInfo { endCursor hasNextPage }} }'
);
 
echo '<pre>';
echo json_encode($processor->getResponseData()) . "\n";
echo '<pre>';

Access the page from the browser of your choice via the URL http://your-local-php-server/tutorial-pagination.php.

You should expect a JSON output as shown below if you have followed this tutorial correctly:

{
    "data": {
        "articlesConnection": {
            "edges": [
                {
                    "node": {
                        "title": "GraphQL vs Rest"
                },
                    "cursor": "YXJyYXljb25uZWN0aW9uOjI="
                },
                {
                    "node": {
                        "title": "GraphQL vs Rest Round 1"
                },
                    "cursor": "YXJyYXljb25uZWN0aW9uOjM="
                }
            ],
        "pageInfo": {
                "endCursor": "YXJyYXljb25uZWN0aW9uOjM=",
                "hasNextPage": true
            }
        }
    }
}

Try to change different arguments (first, after, last and before) and see how GraphQL cursor based pagination reflects.

The End

Next tutorial, we will implement sort feature. If you like our post, please follow us on Twitter and help spread the word. We need your support to continue. If you have questions or find our mistakes in above tutorial, do leave a comment below to let us know.