This quick tutorial walks you through building an API with Laravel and GraphQL API. It covers authentication, querying nested resources and eager loading of related models.
We’ll be diving deeper into working with GraphQL and Laravel by building an API with GraphQL and Laravel. This will cover things like authentication, querying nested resources and eager loading related models.
This tutorial assumes the following:
Also, ensure you have the following installed:
We’ll be building an API for a microblogging platform with which users can share small code snippets. Users will be able to edit and delete their own snippets. Also, users will be able to reply to and like snippets posted by other users. Some of these features will be restricted to only authenticated users.
To save us some time, I have created a basic Laravel project with the laravel-graphql and the jwt-auth packages installed and set up. The jwt-auth
package will allow us to use JSON Web Tokens (JWT) for authentication.
Let’s clone it from GitHub:
1$ git clone -b start https://github.com/ammezie/codebits-api.git
Once that’s done, let’s install the project’s dependencies:
1cd codebits-api 2 $ composer install
Rename .env.example
to .env
. Lastly, run the commands below to generate the app key and JWT secret respectively:
1$ php artisan key:generate 2 $ php artisan jwt:secret
In addition to the User model that comes with Laravel by default, we’ll be creating three more models and their corresponding migration files: Bit, Reply, Like.
1$ php artisan make:model Bit -m 2 $ php artisan make:model Reply -m 3 $ php artisan make:model Like -m
Next, let’s open the migration file generated for the Bit model and update the up
method as below:
1// database/migrations/TIMESTAMP_create_bits_table.php 2 3 public function up() 4 { 5 Schema::create('bits', function (Blueprint $table) { 6 $table->increments('id'); 7 $table->unsignedInteger('user_id'); 8 $table->text('snippet'); 9 $table->timestamps(); 10 }); 11 }
We’ll do the same for both the Like and Reply migration files respectively:
1// database/migrations/TIMESTAMP_create_likes_table.php 2 3 public function up() 4 { 5 Schema::create('likes', function (Blueprint $table) { 6 $table->unsignedInteger('user_id'); 7 $table->unsignedInteger('bit_id'); 8 }); 9 }
1// database/migrations/TIMESTAMP_create_replies_table.php 2 3 public function up() 4 { 5 Schema::create('replies', function (Blueprint $table) { 6 $table->increments('id'); 7 $table->unsignedInteger('user_id'); 8 $table->unsignedInteger('bit_id'); 9 $table->string('reply'); 10 $table->timestamps(); 11 }); 12 }
Before we run the migrations, let’s set up our database. We’ll be using SQLite. So, create a database.sqlite
file inside the database
directory then update the .env
file as below:
1// .env 2 3 DB_CONNECTION=sqlite 4 DB_DATABASE=ABSOLUTE_PATH_TO_DATABASE_SQLITE_FILE
Run the migrations:
1$ php artisan migrate
You will notice the likes
table doesn’t have the timestamps (created_at
and updated_at
) fields. So we need to make Eloquent aware we are not using timestamps for this particular table. To do that, add the line below to the Like model:
1// app/Like.php 2 3 public $timestamps = false;
Let ’s define the relationships between our models. We’ll be defining only the necessary parts of the relationships needed for the purpose of this tutorial. We’ll start by defining the relationship between a user and a bit, which will be a one-to-many relationship. That is, a user can add as many bits as they wish, but a bit can only belong to one user. Add the code below inside the User model (that is, the User class):
1// app/User.php 2 3 public function bits() 4 { 5 return $this->hasMany(Bit::class); 6 }
Next, let’s define the inverse of the relationship on the Bit model (that is, the Bit class):
1// app/Bit.php 2 3 public function user() 4 { 5 return $this->belongsTo(User::class); 6 }
Users can leave replies on a bit, hence a bit can have many replies. A reply can only belong to one bit. This is also a one-to-many relationship. Add the code below to the Bit model (that is, the Bit class):
1// app/Bit.php 2 3 public function replies() 4 { 5 return $this->hasMany(Reply::class); 6 }
Let’s define the inverse of the relationship on the Reply model (that is, the Reply class):
1// app/Reply.php 2 3 public function bit() 4 { 5 return $this->belongsTo(Bit::class); 6 }
In the same vein, a bit can have many likes. Add the code below to the Bit model (that is, the Bit class):
1// app/Bit.php 2 3 public function likes() 4 { 5 return $this->hasMany(Like::class); 6 }
Lastly, a reply can be made by a user. Add the code below to the Reply model (that is, the Reply class):
1// app/Reply.php 2 3 public function user() 4 { 5 return $this->belongsTo(User::class); 6 }
We’ll be creating GraphQL types corresponding to our models with the exception of the Like model, which we won’t be creating a type for. Create a new GraphQL
directory within the app
directory. Within the GraphQL
directory, create a new Type
directory. Within app/GraphQL/Type
directory, create a new UserType.php
file and paste the following code in it:
1// app/GraphQL/Type/UserType.php 2 3 <?php 4 5 namespace App\GraphQL\Type; 6 7 use GraphQL; 8 use GraphQL\Type\Definition\Type; 9 use Folklore\GraphQL\Support\Type as GraphQLType; 10 11 class UserType extends GraphQLType 12 { 13 protected $attributes = [ 14 'name' => 'User', 15 'description' => 'A user' 16 ]; 17 18 public function fields() 19 { 20 return [ 21 'id' => [ 22 'type' => Type::nonNull(Type::int()), 23 'description' => 'The id of a user' 24 ], 25 'name' => [ 26 'type' => Type::nonNull(Type::string()), 27 'description' => 'The name of a user' 28 ], 29 'email' => [ 30 'type' => Type::nonNull(Type::string()), 31 'description' => 'The email address of a user' 32 ], 33 'bits' => [ 34 'type' => Type::listOf(GraphQL::type('Bit')), 35 'description' => 'The user bits' 36 ], 37 'created_at' => [ 38 'type' => Type::string(), 39 'description' => 'Date a was created' 40 ], 41 'updated_at' => [ 42 'type' => Type::string(), 43 'description' => 'Date a was updated' 44 ], 45 ]; 46 } 47 48 protected function resolveCreatedAtField($root, $args) 49 { 50 return (string) $root->created_at; 51 } 52 53 protected function resolveUpdatedAtField($root, $args) 54 { 55 return (string) $root->updated_at; 56 } 57 }
The UserType
contains the exact same fields corresponding to those in the users
tables. It also contains an additional bits
field, which is of BitType
(we’ll create this shortly). This will allow us to retrieve the bits (using the relationship defined above) that a user has posted. Lastly, because Laravel will automatically cast the created_at
and the updated_at
fields to an instance of carbon, we need to specifically define how we want these fields to be resolved. So we define a resolve function for each of the fields by using the convention resolve[FIELD_NAME]Field
. As you can see, we are simply casting the fields to string.
Now, let’s create the BitType
. Within app/GraphQL/Type
directory, create a new BitType.php
file and paste the following code in it:
1// app/GraphQL/Type/BitType.php 2 3 <?php 4 5 namespace App\GraphQL\Type; 6 7 use GraphQL; 8 use GraphQL\Type\Definition\Type; 9 use Folklore\GraphQL\Support\Type as GraphQLType; 10 11 class BitType extends GraphQLType 12 { 13 protected $attributes = [ 14 'name' => 'Bit', 15 'description' => 'Code bit' 16 ]; 17 18 public function fields() 19 { 20 return [ 21 'id' => [ 22 'type' => Type::nonNull(Type::int()), 23 'description' => 'The id of a bit' 24 ], 25 'user' => [ 26 'type' => Type::nonNull(GraphQL::type('User')), 27 'description' => 'The user that posted a bit' 28 ], 29 'snippet' => [ 30 'type' => Type::nonNull(Type::string()), 31 'description' => 'The code bit' 32 ], 33 'created_at' => [ 34 'type' => Type::string(), 35 'description' => 'Date a bit was created' 36 ], 37 'updated_at' => [ 38 'type' => Type::string(), 39 'description' => 'Date a bit was updated' 40 ], 41 'replies' => [ 42 'type' => Type::listOf(GraphQL::type('Reply')), 43 'description' => 'The replies to a bit' 44 ], 45 'likes_count' => [ 46 'type' => Type::int(), 47 'description' => 'The number of likes on a bit' 48 ], 49 ]; 50 } 51 52 protected function resolveCreatedAtField($root, $args) 53 { 54 return (string) $root->created_at; 55 } 56 57 protected function resolveUpdatedAtField($root, $args) 58 { 59 return (string) $root->updated_at; 60 } 61 62 protected function resolveLikesCountField($root, $args) 63 { 64 return $root->likes->count(); 65 } 66 }
Same as we did with the UserType
. We define additional fields (user
, replies
, and likes_count
). The user
field is of the UserType
, which will be used to retrieve the user (using the relationship defined above) that posted the bit. Also, the replies
field is of ReplyType
(we’ll create this shortly), which will be used to retrieve the replies (using the relationship defined above) that have been left on the bit. The likes_count
field will be used to retrieve the number of likes the bit has. We define how the likes_count
field will be resolved. Again, we use the relationship defined above to retrieve the bit likes and return the count.
Let’s create our last type, which will be ReplyType
. Within app/GraphQL/Type
directory, create a new ReplyType.php
file and paste the following code in it:
1// app/GraphQL/Type/ReplyType.php 2 3 <?php 4 5 namespace App\GraphQL\Type; 6 7 use GraphQL; 8 use GraphQL\Type\Definition\Type; 9 use Folklore\GraphQL\Support\Type as GraphQLType; 10 11 class ReplyType extends GraphQLType 12 { 13 protected $attributes = [ 14 'name' => 'Reply', 15 'description' => 'Reply to codebit' 16 ]; 17 18 public function fields() 19 { 20 return [ 21 'id' => [ 22 'type' => Type::nonNull(Type::int()), 23 'description' => 'The id of a reply' 24 ], 25 'user' => [ 26 'type' => Type::nonNull(GraphQL::type('User')), 27 'description' => 'The user that posted a reply' 28 ], 29 'bit' => [ 30 'type' => Type::nonNull(GraphQL::type('Bit')), 31 'description' => 'The bit that was replied to' 32 ], 33 'reply' => [ 34 'type' => Type::nonNull(Type::string()), 35 'description' => 'The reply' 36 ], 37 'created_at' => [ 38 'type' => Type::string(), 39 'description' => 'Date a bit was created' 40 ], 41 'updated_at' => [ 42 'type' => Type::string(), 43 'description' => 'Date a bit was updated' 44 ], 45 ]; 46 } 47 48 protected function resolveCreatedAtField($root, $args) 49 { 50 return (string) $root->created_at; 51 } 52 53 protected function resolveUpdatedAtField($root, $args) 54 { 55 return (string) $root->updated_at; 56 } 57 }
This follows the same approach as the other types, with additional fields (user
and bit
) to retrieve the user that left the reply and the bit the reply was left on respectively.
Now, let’s make GraphQL aware of our types by adding them to config/graphql.php
:
1// config/graphql.php 2 3 'types' => [ 4 'User' => \App\GraphQL\Type\UserType::class, 5 'Bit' => \App\GraphQL\Type\BitType::class, 6 'Reply' => \App\GraphQL\Type\ReplyType::class, 7 ],
With our types created, let’s allow users to sign up. For this, we’ll create a mutation, which we’ll call SignUpMutation
. Create a new Mutation
directory within app/GraphQL
, and within Mutation
, create a new SignUpMutation.php
file and paste the following code into it:
1// app/GraphQL/Mutation/SignUpMutation.php 2 3 <?php 4 5 namespace App\GraphQL\Mutation; 6 7 use GraphQL\Type\Definition\Type; 8 use Folklore\GraphQL\Support\Mutation; 9 use App\User; 10 11 class SignUpMutation extends Mutation 12 { 13 protected $attributes = [ 14 'name' => 'signUp' 15 ]; 16 17 public function type() 18 { 19 return Type::string(); 20 } 21 22 public function args() 23 { 24 return [ 25 'name' => [ 26 'name' => 'name', 27 'type' => Type::nonNull(Type::string()), 28 'rules' => ['required'], 29 ], 30 'email' => [ 31 'name' => 'email', 32 'type' => Type::nonNull(Type::string()), 33 'rules' => ['required', 'email', 'unique:users'], 34 ], 35 'password' => [ 36 'name' => 'password', 37 'type' => Type::nonNull(Type::string()), 38 'rules' => ['required'], 39 ], 40 ]; 41 } 42 43 public function resolve($root, $args) 44 { 45 $user = User::create([ 46 'name' => $args['name'], 47 'email' => $args['email'], 48 'password' => bcrypt($args['password']), 49 ]); 50 51 // generate token for user and return the token 52 return auth()->login($user); 53 } 54 }
We define the type this mutation will return, then define an args
method that returns an array of arguments that the mutation can accept. We also define some validation rules for each of the argument. The resolve
method handles the actual creating of the user and persisting into the database. Lastly, we log the new user in by generating a token (JWT), then we return the token.
Next, let’s add the mutation to config/graphql.php
:
1// config/graphql.php 2 3 'schemas' => [ 4 'default' => [ 5 // ... 6 'mutation' => [ 7 'signUp' => \App\GraphQL\Mutation\SignUpMutation::class, 8 ] 9 ] 10 ],
Returning users should be able to log in, let’s create a LogInMutation
for that. Create a new LogInMutation.php
file within app/GraphQL/Mutation
and paste the following code into it:
1// app/GraphQL/Mutation/LogInMutation.php 2 3 <?php 4 5 namespace App\GraphQL\Mutation; 6 7 use GraphQL\Type\Definition\Type; 8 use Folklore\GraphQL\Support\Mutation; 9 10 class LogInMutation extends Mutation 11 { 12 protected $attributes = [ 13 'name' => 'logIn' 14 ]; 15 16 public function type() 17 { 18 return Type::string(); 19 } 20 21 public function args() 22 { 23 return [ 24 'email' => [ 25 'name' => 'email', 26 'type' => Type::nonNull(Type::string()), 27 'rules' => ['required', 'email'], 28 ], 29 'password' => [ 30 'name' => 'password', 31 'type' => Type::nonNull(Type::string()), 32 'rules' => ['required'], 33 ], 34 ]; 35 } 36 37 public function resolve($root, $args) 38 { 39 $credentials = [ 40 'email' => $args['email'], 41 'password' => $args['password'] 42 ]; 43 44 $token = auth()->attempt($credentials); 45 46 if (!$token) { 47 throw new \Exception('Unauthorized!'); 48 } 49 50 return $token; 51 } 52 }
This is similar to the SignUpMutation
. It accepts the user email address and password. Using these credentials, it attempts to log the user in. If the credentials are valid, a token is generated, which is in turn returned. Otherwise, we throw an exception.
Next, add the mutation to config/graphql.php
:
1// config/graphql.php 2 3 'schemas' => [ 4 'default' => [ 5 // ... 6 'mutation' => [ 7 ..., 8 'logIn' => \App\GraphQL\Mutation\LogInMutation::class, 9 ] 10 ] 11 ],
Once a user is authenticated, the user will be able to post a new bit. Let’s create the mutation for posting a new bit. Within app/GraphQL/Mutation
, create a new NewBitMutation.php
file and paste the following code into it:
1// app/GraphQL/Mutation/NewBitMutation.php 2 3 <?php 4 5 namespace App\GraphQL\Mutation; 6 7 use GraphQL; 8 use App\Bit; 9 use GraphQL\Type\Definition\Type; 10 use Folklore\GraphQL\Support\Mutation; 11 12 class NewBitMutation extends Mutation 13 { 14 protected $attributes = [ 15 'name' => 'newBit' 16 ]; 17 18 public function type() 19 { 20 return GraphQL::type('Bit'); 21 } 22 23 public function args() 24 { 25 return [ 26 'snippet' => [ 27 'name' => 'snippet', 28 'type' => Type::nonNull(Type::string()), 29 'rules' => ['required'], 30 ], 31 ]; 32 } 33 34 public function authenticated($root, $args, $currentUser) 35 { 36 return !!$currentUser; 37 } 38 39 public function resolve($root, $args) 40 { 41 $bit = new Bit(); 42 43 $bit->user_id = auth()->user()->id; 44 $bit->snippet = $args['snippet']; 45 $bit->save(); 46 47 return $bit; 48 } 49 }
This mutation returns a BitType
and accepts just one argument, which is the code snippet – this is required. Since only authenticated users can post a new bit, we define an authenticated
method whose third parameter will be the currently authenticated user or null
if a user is not logged in. So we simply cast whatever currentUser
holds to boolean. That is, the authenticated
method will return true
if the user is authenticated and hence proceed with the rest of the mutation. If the method returns false
, the mutation will throw an “Unauthenticated” error message. In the resolve
method, we create a new bit using the ID of the currently authenticated user and the snippet supplied, then persist it to the database. Finally, we return the newly created bit.
Next, add the mutation to config/graphql.php
:
1// config/graphql.php 2 3 'schemas' => [ 4 'default' => [ 5 // ... 6 'mutation' => [ 7 ..., 8 'newBit' => \App\GraphQL\Mutation\NewBitMutation::class, 9 ] 10 ] 11 ],
Let’s move on to creating our first query. This query will be used to fetch all the bits that have been posted. Create a new Query
directory inside app/GraphQL
, and within the Query
directory, create a new AllBitsQuery.php
file and paste the following code in it:
1// app/GraphQL/Query/AllBitsQuery.php 2 3 <?php 4 5 namespace App\GraphQL\Query; 6 7 use GraphQL; 8 use App\Bit; 9 use GraphQL\Type\Definition\Type; 10 use Folklore\GraphQL\Support\Query; 11 use GraphQL\Type\Definition\ResolveInfo; 12 13 class AllBitsQuery extends Query 14 { 15 protected $attributes = [ 16 'name' => 'allBits' 17 ]; 18 19 public function type() 20 { 21 return Type::listOf(GraphQL::type('Bit')); 22 } 23 24 public function resolve($root, $args, $context, ResolveInfo $info) 25 { 26 $fields = $info->getFieldSelection(); 27 28 $bits = Bit::query(); 29 30 foreach ($fields as $field => $keys) { 31 if ($field === 'user') { 32 $bits->with('user'); 33 } 34 35 if ($field === 'replies') { 36 $bits->with('replies'); 37 } 38 39 if ($field === 'likes_count') { 40 $bits->with('likes'); 41 } 42 } 43 44 return $bits->latest()->get(); 45 } 46 }
We define the query type to be a list of the BitType
. Then we define the resolve
method, which will handle the actual fetching of the bits. We use a getFieldSelection
method to get the names of all fields selected when the query is run. Using these fields, we eager load the respective related models then return the bits from the most recently added.
Let’s add the query to config/graphql.php
:
1// config/graphql.php 2 3 'schemas' => [ 4 'default' => [ 5 'query' => [ 6 'allBits' => \App\GraphQL\Query\AllBitsQuery::class, 7 ], 8 // ... 9 ] 10 ],
Let’s add the query to fetch a single bit by it ID. Within app/GraphQL/Query
, create a new BitByIdQuery.php
file and paste the following code into it:
1// app/GraphQL/Query/BitByIdQuery.php 2 3 <?php 4 5 namespace App\GraphQL\Query; 6 7 use GraphQL; 8 use App\Bit; 9 use GraphQL\Type\Definition\Type; 10 use Folklore\GraphQL\Support\Query; 11 12 class BitByIdQuery extends Query 13 { 14 protected $attributes = [ 15 'name' => 'bitById' 16 ]; 17 18 public function type() 19 { 20 return GraphQL::type('Bit'); 21 } 22 23 public function args() 24 { 25 return [ 26 'id' => [ 27 'name' => 'id', 28 'type' => Type::nonNull(Type::int()), 29 'rules' => ['required'] 30 ], 31 ]; 32 } 33 34 public function resolve($root, $args) 35 { 36 if (!$bit = Bit::find($args['id'])) { 37 throw new \Exception('Resource not found'); 38 } 39 40 return $bit; 41 } 42 }
This query accepts a single argument, which is required. In the resolve
method, we fetch a bit whose ID matches the id
argument supplied. If none is found, we throw an exception. Otherwise, we return the bit.
Add the query to config/graphql.php
:
1// config/graphql.php 2 3 'schemas' => [ 4 'default' => [ 5 'query' => [ 6 'bitById' => \App\GraphQL\Query\BitByIdQuery::class, 7 ], 8 // ... 9 ] 10 ],
Within app/GraphQL/Mutation
, create a new ReplyBitMutation.php
file and paste the following code into it:
1// app/GraphQL/Mutation/ReplyBitMutation.php 2 3 <?php 4 5 namespace App\GraphQL\Mutation; 6 7 use GraphQL; 8 use App\Bit; 9 use App\Reply; 10 use GraphQL\Type\Definition\Type; 11 use Folklore\GraphQL\Support\Mutation; 12 13 class ReplyBitMutation extends Mutation 14 { 15 protected $attributes = [ 16 'name' => 'replyBit' 17 ]; 18 19 public function type() 20 { 21 return GraphQL::type('Reply'); 22 } 23 24 public function args() 25 { 26 return [ 27 'bit_id' => [ 28 'name' => 'bit_id', 29 'type' => Type::nonNull(Type::int()), 30 'rules' => ['required'], 31 ], 32 'reply' => [ 33 'name' => 'reply', 34 'type' => Type::nonNull(Type::string()), 35 'rules' => ['required'], 36 ], 37 ]; 38 } 39 40 public function authenticated($root, $args, $currentUser) 41 { 42 return !!$currentUser; 43 } 44 45 public function resolve($root, $args) 46 { 47 $bit = Bit::find($args['bit_id']); 48 49 $reply = new Reply(); 50 $reply->user_id = auth()->user()->id; 51 $reply->reply = $args['reply']; 52 53 $bit->replies()->save($reply); 54 55 return $reply; 56 } 57 }
This returns a ReplyType
and accepts two arguments: the ID of the bit the reply is for and the reply content. It is also restricted to only authenticated users. In the resolve
method, we fetch the bit that the reply is for. Then we create a new reply containing the ID of the user leaving the reply and the reply content. Using the replies
relationship, we persist in the new reply to the database. Finally, we return the newly created reply.
Add the mutation to config/graphql.php
:
1// config/graphql.php 2 3 'schemas' => [ 4 'default' => [ 5 // ... 6 'mutation' => [ 7 ..., 8 'replyBit' => \App\GraphQL\Mutation\ReplyBitMutation::class, 9 ] 10 ] 11 ],
The last piece of functionality we’ll be adding is the ability for users to be able to like and unlike bits. For these, we’ll create two new mutations, which will be restricted to only authenticated users. First, the mutation to like a bit. Within app/GraphQL/Mutation
, create a new LikeBitMutation.php
file and paste the following code into it:
1// app/GraphQL/Mutation/LikeBitMutation.php 2 3 <?php 4 5 namespace App\GraphQL\Mutation; 6 7 use App\Like; 8 use GraphQL\Type\Definition\Type; 9 use Folklore\GraphQL\Support\Mutation; 10 use App\Bit; 11 12 class LikeBitMutation extends Mutation 13 { 14 protected $attributes = [ 15 'name' => 'likeBit' 16 ]; 17 18 public function type() 19 { 20 return Type::string(); 21 } 22 23 public function args() 24 { 25 return [ 26 'bit_id' => [ 27 'name' => 'bit_id', 28 'type' => Type::nonNull(Type::int()), 29 'rules' => ['required'], 30 ], 31 ]; 32 } 33 34 public function authenticated($root, $args, $currentUser) 35 { 36 return !!$currentUser; 37 } 38 39 public function resolve($root, $args) 40 { 41 $bit = Bit::find($args['bit_id']); 42 43 $like = new Like(); 44 $like->user_id = auth()->user()->id; 45 $bit->likes()->save($like); 46 47 return 'Like successful!'; 48 } 49 }
This a accepts the ID of the bit as its only argument. The resolve
method is similar to that of the ReplyBitMutation
, we fetch the bit then create and persist the like to the database. We then return a success message.
Next, let’s create the mutation to, unlike a bit. Within app/GraphQL/Mutation
, create a new UnlikeBitMutation.php
file and paste the following code into it:
1// app/GraphQL/Mutation/UnlikeBitMutation.php 2 3 <?php 4 5 namespace App\GraphQL\Mutation; 6 7 use App\Like; 8 use GraphQL\Type\Definition\Type; 9 use Folklore\GraphQL\Support\Mutation; 10 11 class UnlikeBitMutation extends Mutation 12 { 13 protected $attributes = [ 14 'name' => 'unlikeBit' 15 ]; 16 17 public function type() 18 { 19 return Type::string(); 20 } 21 22 public function args() 23 { 24 return [ 25 'bit_id' => [ 26 'name' => 'bit_id', 27 'type' => Type::nonNull(Type::int()), 28 'rules' => ['required'], 29 ], 30 ]; 31 } 32 33 public function authenticated($root, $args, $currentUser) 34 { 35 return !!$currentUser; 36 } 37 38 public function resolve($root, $args) 39 { 40 $like = Like::where('user_id', auth()->user()->id) 41 ->where('bit_id', $args['bit_id']) 42 ->delete(); 43 44 return 'Unlike successful!'; 45 } 46 }
This mutation is pretty straightforward as it deletes a like where the user ID matches that of the authenticated user and where the bit ID matches that supplied in the argument. Finally, returns a success message.
Let’s add them to config/graphql.php
:
1// config/graphql.php 2 3 'schemas' => [ 4 'default' => [ 5 // ... 6 'mutation' => [ 7 ..., 8 'likeBit' => \App\GraphQL\Mutation\LikeBitMutation::class, 9 'unlikeBit' => \App\GraphQL\Mutation\UnlikeBitMutation::class, 10 ] 11 ] 12 ],
We need to make sure the server is up and running. Let’s start the server:
1$ php artisan serve
We can test our API directly in the browser as below:
1// fetch all bits 2 3 http://127.0.0.1:8000/graphql?query=query{allBits{id,user{name},snippet,replies{id,user{name},reply},likes_count,created_at,updated_at}} 4 5 // post a new bit 6 http://127.0.0.1:8000/graphql?query=mutation{newBit(snippet: "<?php ehco phpinfo(); ?>"){id,user{id,name},snippet,created_at,updated_at}}
But I prefer to test with Insomnia because it has support for GraphQL out of the box. The query to fetch all bits and their nested queries might look like below:
1// fetch all bits 2 3 { 4 allBits { 5 id 6 user { 7 name 8 } 9 snippet 10 replies { 11 id 12 user { 13 name 14 } 15 reply 16 } 17 likes_count 18 created_at 19 updated_at 20 } 21 }
Then we’ll get a result as in the images below in Insomnia:
Similarly, the mutation to post a new bit:
1// post a new bit 2 3 mutation { 4 newBit (snippet: "<?php ehco phpinfo(); ?>") { 5 id 6 user { 7 id 8 name 9 } 10 snippet 11 created_at 12 updated_at 13 } 14 }
As you can see from the image below, the mutation returns a authenticated error because we are not authenticated.
We need to add the token that was returned upon log in to the Authorization header of the mutation request. From the Auth dropdown, select Bearer Token and paste the token (JWT) in the field provider.
With that added, we can now post a new bit successfully.
In this tutorial, we have seen how to build an API using Laravel and GraphQL. We covered things like authentication, querying nested resources and eager loading related models. Feel free to play with this API by adding your own features, this will help solidify your understanding of working with Laravel and GraphQL. Why not try adding features to edit and delete a bit respectively.
Do check the laravel-graphql repository to learn more about the package. The complete code for this tutorial is available on GitHub.