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.
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
]
];
}
}
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.
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:
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.
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.