First steps with GraphQL in Laravel Framework - Part One

 Reading time ~9 minutes

Heads up: this article is over a year old. Some information might be out of date, as I don't always update older articles.

Introduction

GraphQL1 is one of the recent major technologies introduced by Facebook in the React ecosystem. Essentially it is a declarative approach for querying data from a backend.

“React is changing the way we build complex client-side UI applications. Flux rethinks the flow of data through applications. GraphQL is changing the way we fetch our data from the server.”

In a nutshell, GraphQL is challenging REST. The key concept is that the client is the only component of an application that knows best what data is needed for rendering the UI. This means that each part of the UI can fetch declaratively from the server the exact data that it needs, without affecting other parts of the application.

I’m sure that this is a common scenario: you set up a REST endpoint for fetching the users of your application in the administrator section. Later you want to use the same endpoint in the public section, but soon you realize that you need only the name and surname of the users, but the endpoint returns a whole bunch of additional information. This is an overfetching problem, where unnecessary data is sent to the client each time it requests the list of users. You could decide to fetch only the data needed in the public section, but you will end up with an underfetching bug, affecting the admin rendering UI. Unfortunately REST doesn’t provide a solution out of the box. You could use complex url parameters to differentiate the requests or use different endpoints, but this will violate the REST principles, as well as introducing unwanted complexity to the application.

In most of the cases, even if your intention is to build a REST interface as flexible as possible, trying to avoid complex logic on your server, after some coding you realize that it cannot be done otherwise. So you end up building ad hoc endpoints that couple the data to particular views, violating the most important REST principle.

Facebook devs solved this problem using Relay, a sort of container that defines how a component should render (using React) and what data it needs to render (through a GraphQL representation), switching the logic from the server to the client.

“Relay takes care of composing the data queries into efficient batches, providing each component with exactly the data that it requested (and no more), updating those components when the data changes, and maintaining a client-side store (cache) of all data.”

In this way, if you change the data requirements of your components, you don’t need to touch the server. You can just change the data query and the rendering logic of the component and you’re good to go.

GraphQL

Let’s make an example of a GraphQL query for a Facebook comment (a slightly simplified version, as comment likes are not taken into account).

Facebook comment component

First we have to define what data it needs to render correctly:

{
    "comment" : {
        "id": 112654898798425,
        "text": "We's liked it. Didn't we?",
        "timestamp": "2015-12-09 14:52:55",
        "parent_status": 1234567890,
        "user" : {
            "id": 4000,
            "name": "Smeagol",
            "isViewerFriend": true,
            "profilePicture": {
                "uri": "http://example.com/pic.jpg",
                "width": 50,
                "height": 50
            }
        }
    }
}

A GraphQL query for the previous data could look like this:

{
    comment(id : 112654898798425) {
        id,
        text,
        timestamp,
        parent_status,
        user {
            id,
            name,
            isViewerFriend,
            profilePicture(size: 50)  {
                uri,
                width,
                height
            }
        }
    }
}

Which basically means that you are interested in the comment with id 112654898798425, with fields id, text, timestamp, parent_status and the user relationship with the defined fields.

To reach the same detail level in REST would mean encoding each field as a query parameter with additional syntax for dealing with relationships (user and profilePicture) and subqueries (size : 50). You may end with something like this, using some sort of mapping with the Eloquent syntax:

/comments/112654898798425?
        with=user,user.profilePicture
        field=id&
        field=text&
        field=timestamp&
        field=parent_status&
        field=user.id&
        field=user.name&
        field=user.isViewerFriend&
        field=user.profilePicture.uri&
        field=user.profilePicture.width&
        field=user.profilePicture.height

Not so expressive as the GraphQL syntax and it doesn’t even take into account the subquery on profilePicture that returns only the picture of size 50.

So, following the official GraphQL introduction, we can outline the main benefits of GraphQL:

  • Hierarchical data structure
  • Human/Machine readable because the query is shaped like the data it returns
  • Product(Component)-centric
  • Backend independent, the specification is not tied to a particular programming language or storage engine
  • Application-layer protocol, you can use it with HTTP or WebSockets for example
  • Strongly-typed

Another benefit when using GraphQL instead of REST is that you can use a single HTTP end-point for all the requests.

GraphQL in Laravel

As we said, GraphQL is storage and backend independent therefore it principles can be implemented also in PHP and Laravel.

In this post we will use the Laravel 5 implementation by Folkloreatelier, which is based on the graphql-php library.

Warning: Both libraries are still work-in-progress, the use in production is discouraged.

The installation is straightforward, as any other Laravel package. You can publish the configuration file using the command

$ php artisan vendor:publish --provider="Folklore\GraphQL\GraphQLServiceProvider"

In the configuration file you can tweak most of the parameters of the library.

<?php

'prefix' => 'graphql',

'routes' => [
     'query' => '/query',
     'mutation' => '/mutation'
],

The prefix and routes parameters define the HTTP endpoints for the GraphQL queries. The above parameters make available to your application two distinct URLs (/graphql/query and /graphql/mutation), one for the queries and one for the mutations. I’ve only vaguely started thinking about mutations, so in this post I will consider only queries.

Next you need to create a type for your queries. In this initial phase it’s safe to assume that each type corresponds to a Laravel Model in your application. A type is essentially an object that defines the schema of a GraphQL query. Following the previous example we can write our CommentType class:

<?php

class CommentType extends GraphQLType {

    protected $attributes = [
        'name' => 'Comment',
        'description' => 'The Schema of a Comment Type'
    ];

    public function fields()
    {
        return [
            'id' => [
                'type' => Type::nonNull(Type::id()),
                'description' => 'The id of the comment'
            ],
            'text' => [
                'type' => Type::nonNull(Type::string()),
                'description' => 'The text of the comment'
            ],
            'timestamp' => [
                'type' => Type::nonNull(Type::string()),
                'description' => 'The timestamp of the comment'
            ],
            'parent_status' => [
                'type' => Type::nonNull(Type::int()),
                'description' => 'The parent status of the comment'
            ]
        ];
    }
}

As you can see, you can define which fields can be retrieved by the GraphQL request and their types.

Next you will need to add this class to the config/graphql.php configuration file

<?php

'types' => [
     'comments' => 'App\GraphQL\Type\CommentType'
 ]

Then you have to define a query that returns this type (or a list of them):

<?php

class CommentsQuery extends Query
{
    protected $attributes = [
        'name' => 'Comment query'
    ];

    public function type()
    {
        return Type::listOf(GraphQL::type('comment'));
    }

    public function args()
    {
        return [
            'id' => ['name' => 'id', 'type' => Type::int()]
        ];
    }

    public function resolve($root, $args, ResolveInfo $info)
    {
        if(isset($args['id'])) {
            return Comment::where('id' , $args['id'])->get();
        } else {
            return Comment::all();
        }
    }
}

The code is straightforward. We are binding this query object to the comment type and with the args function we define the fields that can be used to resolve the type. In this example we can filter the request by passing an int as an argument of the id field.

Now you are able to fetch all your comments using this URL

graphql/query?query=query+FetchComments{comments+{id,text,timestamp}}

or fetch a single comment with

graphql/query?query=query+FetchComments{comments(id:112654898798425)+{id,text,timestamp}}

Does it test?

Unit tests on a GraphQL endpoint are relatively easy. I’m using the database migrations for each test case, which allow me to start with a new fresh database instance that I can seed based on my needs.

I noticed in the past that it’s quite challenging trying to mock Eloquent methods on models because most of the ones that we use to query are, in fact, methods of Illuminate\Database\Eloquent\Builder and are called through __callStatic and __call.

So in this case I’m not using mocks, instead I’m calling the database each time in my tests because I’m able to define a context for my assertions using the model factory and the database migrations. This solution it’s acceptable for the purpose of this post, because the tests are not performed in true isolation.

<?php

class GraphQLCommentTest extends TestCase
{
    use DatabaseMigrations;

    public function testFetchComments() {

        $comments = factory(App\Models\Comment::class)->create();

        $this->get('/graphql/query?query=query+FetchComments{comments+{id,text,timestamp,parent_status}}')
            ->seeStatusCode(200)
            ->seeJsonEquals([
                'data' => [
                    'comments' => [
                        [
                            'id' => $comment->id,
                            'text' => $comment->text,
                            'timestamp' => $comment->timestamp,
                            'parent_status' => $comment->parent_status
                        ]
                    ]
                ]
            ]);
    }
}

What about relationships?

A relationship is just another field that corresponds to a type. If we follow our example we can create our UserType like this

<?php

class UserType extends GraphQLType {

  protected $attributes = [
      'name' => 'User',
      'description' => 'The Schema of a User Type'
  ];

  public function fields()
  {
      return [
          'id' => [
              'type' => Type::nonNull(Type::id()),
              'description' => 'The id of the user'
          ],
          'name' => [
              'type' => Type::nonNull(Type::string()),
              'description' => 'The text of the comment'
          ],

          'isViewerFriend' => [
              'type' => Type::nonNull(Type::boolean()),
              'description' => 'Is the viewer friend with this user'
          ]
      ];
  }
}

Notice: isViewerFriend is not a field of our database, instead it is an accessor.

Next you have to define the relationship in the CommentType by adding the following line to the fields function

<?php

'user' => ['type' => GraphQL::type('user'), 'description' => 'The user relationship'],

Finally in the resolve function of the CommentQuery you can use Eloquent’s logic to eager load the relationship:

<?php

public function resolve($root, $args, ResolveInfo $info)
{
    $fields = $info->getFieldSelection($depth = 3);

    $comments = Comment::query();

    foreach ($fields as $field => $keys) {
        if($field === 'user') {
            $comments->with('user');
        }
    }

    if(isset($args['id'])) {
        return $comments->where('id' , $args['id'])->get();
    } else {
        return $comments->get();
    }
}

Basically we are retrieving the keys from the request using a method of the GraphQL\Type\Definition\ResolveInfo instance class. Then we build the query by iterating through each field, loading the relationship if the user key is present.

Sort results

GraphQL itself doesn’t have any notion of sorting. However you can easily implement the appropriate arguments and semantics that you want, in order to perform complex queries.

For example we can add an orderby argument to the CommentQuery class, as follows:

<?php

return [
    'id' => ['name' => 'id', 'type' => Type::int()],
    // [...]
    'orderby' => ['name' => 'orderby', 'type' => Type::listOf(Type::string())]
];

Basically it means that our query now accepts an argument orderby, defined as a list of strings.

You should also edit the resolve function in order to use the new argument:

<?php

public function resolve($root, $args, ResolveInfo $info)
{
    $comments = Comment::query();

    if(isset($args['orderby']))
    {
        $comments->orderBy($args['orderby'][0], $args['orderby'][1]);

    }

    // [...]
}

Finally you can use this new field in our application like any other argument. For example the following URL is perfectly valid:

graphql/query?query=query+FetchComments+{comments(orderby:["id","DESC"])+{id,text,timestamp}}

In the next part I’ll dive into mutations. Stay tuned!


  1. GraphQL is used in production by Facebook for nearly three years now, but just recently they decided to open-source a reference implementation. ↩︎

comments powered by Disqus

JavaScript Quiz Answers Explained

Introduction

JavaScript is notoriously a simple programming language, but let’s be honest: it sucks!

Don’t get me …

Laravel 5 Cron Jobs on shared hosting

Five Programming Problems