Miguel Piedrafita

Miguel Piedrafita

Laravel GraphQL Gotchas

For a new project I'm working on, I had to code a GraphQL API with Laravel. I pulled in the Lighthouse GraphQL Server package, which was featured on Laravel News some weeks ago and started to follow the documentation. While most things worked as I expected, I spent quite a lot of time debugging some that didn't and weren't documented anywhere, so after I figured them out, I thought this article could help others facing the same position.

Password length checking

Imagine you have a createUser mutation that create users. It's using the @bcrypt directive (provided by Lighthouse) to make our life easier. The code is as follows:

type Mutation {
	createUser(
        name: String @rules(apply: ["required"]),
        email: String @rules(apply: ["required", "email", "unique:users,email"]),
        password: String @rules(apply: ["required", "string", "min:6", "max:255"]) @bcrypt
    ): User @create(model: "App\\User")
}

You'd expect a password with two characters to throw an error, but it actually works. Apparently, the client first hashes the password and only then performs validation. As a workaround, you can move the migration's logic to a controller by executing php artisan lighthouse:mutation CreateUser and then manually hashing and creating a User.

<?php

namespace App\Http\GraphQL\Mutations;

use App\User;
use Illuminate\Support\Facades\Hash;
use GraphQL\Type\Definition\ResolveInfo;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;

class CreateUser
{
    public function resolve($rootValue, array $args, GraphQLContext $context = null, ResolveInfo $resolveInfo)
    {
        return User::create([
            'username' => $args['username'],
            'email'    => $args['email'],
            'password' => Hash::make($args['password']),
        ]);
    }
}

Policies

Lighthouse features a @can directive to check if the user's authorized to perform the specified action, but it only works for generic models (meaning you can check if the user has permissions to create models, but you can't check permissions like deletion against a singular model). The workaround for this is, again, move your mutation/query to a controlling, and check for permission there.

<?php

namespace App\Http\GraphQL\Mutations;

use App\SecureResource;
use GraphQL\Type\Definition\ResolveInfo;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;

class DoSecureStuff
{
    public function resolve($rootValue, array $args, GraphQLContext $context = null, ResolveInfo $resolveInfo)
    {
        abort_unless(request()->user()->can('update', SecretResource::find($args['id']));

        // do stuff
    }
}

Organizing routing

If you're using this library in a relatively-large project, your schema.graphql might be a +200 LOC mess. If you wanna tidy it up a little bit, you can divide it into smaller files like user.graphql or pages.graphql and include them in your main schema:

#import user.graphql
#import pages.graphql
#import something-else.graphql

But be careful! You can only have one Query and one Mutation type. You'll have to extend the others:

extend type Query {
	pages: [Page!]! @paginate(type: "paginator", model: "App\\Page")
}

extend type Mutation {
	deletePage(id: ID! @rules(apply: ["required"])): User @delete(model: "App\\Page")
}

Debugging GraphQL errors

This library is exceptionally difficult to debug, because the GraphQL handler catches all the errors to avoid failing to the client, and returns null instead of the result. To overcome this, you can update the config file for the package, located at config/lighthouse.php.

    /*
    |--------------------------------------------------------------------------
    | Debug
    |--------------------------------------------------------------------------
    |
    | Control the debug level as described in http://webonyx.github.io/graphql-php/error-handling/
    | Debugging is only applied if the global Laravel debug config is set to true.
    |
    */
-    'debug' => Debug::INCLUDE_DEBUG_MESSAGE | Debug::INCLUDE_TRACE,
+    'debug' => Debug::INCLUDE_DEBUG_MESSAGE | Debug::RETHROW_INTERNAL_EXCEPTIONS

Conclusion

While it still needs some work, this package makes it incredibly easy to develop GraphQL APIs with Laravel. And, if you're using this library in a project, I'd recommend you to spend five minutes of your time writing an appreciation note to the collaborators. They'll surely appreciate it!