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.
Imagine you have a createUser mutation to, uhhh, 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 hashes the password and 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 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' => bcrypt($args['password']),]);}}
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}}
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")}
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 configuration file for the package (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
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!
Originally published in my blog.