In this tutorial, we’ll be setting up a GraphQL API in Laravel. We’ll cover lots of ground including project initialization, creating models and migrations, installing GraphQL packages, and implementing schemas. In addition to that, we’ll also look at setting up queries and mutations. Furthermore, we’ll dive into registering types, optional database seeding, configuring routes and finally testing the API for functionality.
Laravel is an amazing framework. And as great as it is at building APIs, there's always room for improvement. The good news is that Laravel doesn't need to work alone! By utilizing the power of GraphQL alongside Laravel’s powerful features you get something really special! Developed by Facebook, GraphQL offers us the ability to fetch only the data we need from an API. Dynamic data gathering like this wasn’t possible with traditional REST APIs which returned predefined sets of data.
Utilizing both technologies together allows developers to take advantage of all that Laravel has to offer. From Eloquent ORM and middleware, to authentication and more! Meanwhile driving it all forward with the flexibility and efficiency of GraphQL
Steps to Create and Test Laravel GraphQL API
1. Setting Up Your Laravel Project
This setup requires up and running Laravel environment. Now I have already installed Laravel along with all other required dependencies in the previous article. So I will use the same environment to demonstrate this topic.
First let's create a new project to create our GraphQL API:
sudo mkdir -p /opt/projects sudo chmod 777 /opt/projects cd /opt/projects composer create-project --prefer-dist laravel/laravel laravelGraphqlApi
This command creates a new Laravel project named laravelGraphqlApi
. Navigate inside your project:
cd laravelGraphqlApi
After creating your Laravel project, configure your .env
file with your database connection details. I will fill these based on my environment but you should update these for your environment.
DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=my_laravel_db DB_USERNAME=user1 DB_PASSWORD=Passw0rd
2. Create Models and Migration
When you're starting with a Laravel project, you'll typically generate both the model and its corresponding migration at the same time using Artisan commands. Laravel provides a convenient way to do this with a single command. The User
model is usually created by default in a new Laravel project, but you may need to create the Post
model and its migration.
Use Artisan to generate models for User
and Post
:
php artisan make:model User -m
This may return ERROR Model already exists
. as in most cases a User Model and migration file is available for every project.
php artisan make:model Post -m
Users Table: database/migrations/2014_10_12_000000_create_users_table.php
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
Posts Table: database/migrations/2024_02_21_173246_create_posts_table.php
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->string('title');
$table->text('content');
$table->timestamps();
});
Next to handle relationships and other GraphQL-related functionality, we will have to update User
and Post
Model files:
File: app/Models/User.php
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; class User extends Authenticatable { use HasFactory, Notifiable; /** * The attributes that are mass assignable. * * @var array<int, string> */ protected $fillable = [ 'name', 'email', 'password', ]; /** * The attributes that should be hidden for serialization. * * @var array<int, string> */ protected $hidden = [ 'password', 'remember_token', ]; /** * The attributes that should be cast. * * @var array<string, string> */ protected $casts = [ 'email_verified_at' => 'datetime', ]; /** * Get the posts for the user. */ public function posts() { return $this->hasMany(Post::class); } }
File: app/Models/Post.php
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Post extends Model { use HasFactory; /** * The attributes that are mass assignable. * * @var array<int, string> */ protected $fillable = [ 'title', 'content', 'user_id', ]; /** * Get the user that owns the post. */ public function user() { return $this->belongsTo(User::class); } }
Both models demonstrate how to define relationships in Laravel. The User
model has a posts
method that defines a one-to-many relationship with the Post
model. Similarly, the Post
model has a user
method that defines an inverse one-to-many (belongs to) relationship with the User
model.
Run the migrations. We use fresh
to drop any existing table and perform a fresh migration:
php artisan migrate:fresh
This command executes the migration files in sequence, creating or modifying the database schema as defined.
3. Install GraphQL Package
We can either use nuwave/lighthouse
or rebing/graphql-laravel
. You can choose either based on your preference for schema definition and project needs. Lighthouse is ideal for those who prefer using GraphQL SDL and directives for a concise, declarative approach, offering robust support for real-time data with subscriptions. Rebing suits developers seeking a flexible, Laravel-like coding experience, especially when handling multiple GraphQL schemas or requiring a traditional PHP setup.
We will use rebing/graphql-laravel
for our tutorial and to install the same, run the following command in your Laravel project directory:
composer require rebing/graphql-laravel
After installation, you need to publish the package's configuration file to your Laravel project:
php artisan vendor:publish --provider="Rebing\GraphQL\GraphQLServiceProvider"
This command will create a graphql.php
config file in your config
directory.
4. Implement GraphQL Schemas and Types
Creating GraphQL types in Laravel using the rebing/graphql-laravel
package involves defining PHP classes that describe the structure of your GraphQL objects, including their fields and types.
If not already present, create a GraphQL
directory within your app
directory.
mkdir app/GraphQL
Inside the app/GraphQL
directory, create a subdirectory called Types
to store your type definitions.
mkdir app/GraphQL/Types
Next, you'll create PHP classes for each of your GraphQL types. Let's continue with the User
and Post
examples from before.
Open app/GraphQL/Types
/UserType.php
in your text editor or IDE, and define the class as follows:
<?php
namespace App\GraphQL\Types;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Type as GraphQLType;
use Rebing\GraphQL\Support\Facades\GraphQL;
use App\Models\User;
class UserType extends GraphQLType
{
protected $attributes = [
'name' => 'User',
'description' => 'A user',
'model' => User::class, // Optionally bind the model to the type for automatic resolution
];
public function fields(): array
{
return [
'id' => [
'type' => Type::nonNull(Type::int()),
'description' => 'The id of the user',
],
'name' => [
'type' => Type::string(),
'description' => 'The name of the user',
],
'email' => [
'type' => Type::string(),
'description' => 'The email of the user',
],
'posts' => [
'type' => Type::listOf(GraphQL::type('Post')),
'description' => 'The posts of the user',
],
];
}
}
Similarly define app/GraphQL/Types/PostType.php
:
<?php
namespace App\GraphQL\Types;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Type as GraphQLType;
use Rebing\GraphQL\Support\Facades\GraphQL;
use App\Models\Post;
class PostType extends GraphQLType
{
protected $attributes = [
'name' => 'Post',
'description' => 'A post',
'model' => Post::class,
];
public function fields(): array
{
return [
'id' => [
'type' => Type::nonNull(Type::int()),
'description' => 'The id of the post',
],
'title' => [
'type' => Type::string(),
'description' => 'The title of the post',
],
'content' => [
'type' => Type::string(),
'description' => 'The content of the post',
],
'user' => [
'type' => GraphQL::type('User'),
'description' => 'The author of the post',
],
];
}
}
5. Setting Up Queries and Mutations
Queries and mutations are two of the most important aspects in a GraphQL API. Queries let clients grab data from a server, specifying exactly what they need, while mutations give them the ability to update or change any information. This process involves setting up operations your API supports and implementing how these operations work within your database that you use as your data source.
What is our objective?
- Defining types in GraphQL: Here, we need to define the types in our GraphQL schema. This usually means two things; the data types such as
User
andPost
, and also the operation types (queries and mutations). For each type, there are fields that dictate how data can be queried or how it should be entered during a mutation. - Implementing resolvers: In this stage, we will implement resolvers for all queries and mutations. A resolver is a function that fetches data for a query or executes changes like those required by a mutation. In Laravel, we often use Eloquent models to retrieve or modify database data.
- Registering operations: As soon as we have defined our types and implemented resolvers, we register these operations on our GraphQL schema. Thanks to this step, clients can call them up through the API endpoint of their GraphQL enabled app!
Here is a structure of files which we created earlier and the files which we plan to create in this section:
app/ ├── GraphQL │ ├── Mutations │ │ ├── CreatePostMutation.php │ │ └── CreateUserMutation.php │ ├── Queries │ │ ├── PostsQuery.php │ │ └── UsersQuery.php │ └── Types │ ├── PostType.php │ └── UserType.php
File: app/GraphQL/Queries/UsersQuery.php
<?php
namespace App\GraphQL\Queries;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Query;
use Rebing\GraphQL\Support\Facades\GraphQL;
use App\Models\User;
class UsersQuery extends Query
{
protected $attributes = [
'name' => 'users',
];
public function type(): Type
{
return Type::listOf(GraphQL::type('User'));
}
public function resolve($root, $args)
{
return User::all();
}
}
File: app/GraphQL/Queries/PostsQuery.php
<?php
namespace App\GraphQL\Queries;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Query;
use Rebing\GraphQL\Support\Facades\GraphQL;
use App\Models\Post;
class PostsQuery extends Query
{
protected $attributes = [
'name' => 'posts',
];
public function type(): Type
{
return Type::listOf(GraphQL::type('Post'));
}
public function resolve($root, $args)
{
return Post::all();
}
}
File: app/GraphQL/Mutations/CreateUserMutation.php
<?php
namespace App\GraphQL\Mutations;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Mutation;
use Rebing\GraphQL\Support\Facades\GraphQL;
use App\Models\User;
class CreateUserMutation extends Mutation
{
protected $attributes = [
'name' => 'createUser',
];
public function type(): Type
{
return GraphQL::type('User');
}
public function args(): array
{
return [
'name' => [
'type' => Type::nonNull(Type::string()),
],
'email' => [
'type' => Type::nonNull(Type::string()),
],
'password' => [
'type' => Type::nonNull(Type::string()),
],
];
}
public function resolve($root, $args)
{
$user = new User();
$user->fill([
'name' => $args['name'],
'email' => $args['email'],
'password' => bcrypt($args['password']),
]);
$user->save();
return $user;
}
}
File: app/GraphQL/Mutations/CreatePostMutation.php
<?php
namespace App\GraphQL\Mutations;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Mutation;
use Rebing\GraphQL\Support\Facades\GraphQL;
use App\Models\Post;
class CreatePostMutation extends Mutation
{
protected $attributes = [
'name' => 'createPost',
];
public function type(): Type
{
return GraphQL::type('Post');
}
public function args(): array
{
return [
'title' => [
'type' => Type::nonNull(Type::string()),
],
'content' => [
'type' => Type::nonNull(Type::string()),
],
'user_id' => [
'type' => Type::nonNull(Type::int()),
],
];
}
public function resolve($root, $args)
{
$post = new Post();
$post->fill([
'title' => $args['title'],
'content' => $args['content'],
'user_id' => $args['user_id'],
]);
$post->save();
return $post;
}
}
6. Registering Types, Queries and Mutations
The schemas array you’ll be working with in the config/graphql.php
configuration file is used to define the different arrays that your GraphQL server will use. Each one of these can have their own queries, mutations, types and middleware. Being able to do this is an important part of building a strong server. It allows you to expose your code to different clients based on what they are asking for and it makes maintaining multiple schemas easier too.
Underneath the types key in a schema, you reference everything from object types all the way down to User
and Post
types. You do this in order to make sure everything lines up correctly so that each piece of code knows where it belongs in any query or mutation.
'schemas' => [
'default' => [
'query' => [
'users' => App\GraphQL\Queries\UsersQuery::class,
'posts' => App\GraphQL\Queries\PostsQuery::class,
],
'mutation' => [
'createUser' => App\GraphQL\Mutations\CreateUserMutation::class,
'createPost' => App\GraphQL\Mutations\CreatePostMutation::class,
],
'types' => [
'User' => App\GraphQL\Types\UserType::class,
'Post' => App\GraphQL\Types\PostType::class,
],
],
],
This structure ensures that your User
and Post
types are registered and available for use within your default schema, alongside the specific queries and mutations we've defined.
7. Seed the Database (Optional)
This is optional as if you wish to seed some data into the database to test the API then you can follow this section or skip to next section.
Create factories for User and Post models:
php artisan make:factory UserFactory --model=User php artisan make:factory PostFactory --model=Post
Update the factory files in database/factories
to generate dummy data. Laravel uses factories to create test data for your database, which is particularly useful for development and testing purposes. Below are examples of how to update these factory files for your User
and Post
models.
For the User
model, update the factory database/factories/UserFactory.php
to generate users with names, email addresses, and passwords.
<?php namespace Database\Factories; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Str; use App\Models\User; class UserFactory extends Factory { /** * The name of the factory's corresponding model. * * @var string */ protected $model = User::class; /** * Define the model's default state. * * @return array */ public function definition() { return [ 'name' => $this->faker->name(), 'email' => $this->faker->unique()->safeEmail(), 'email_verified_at' => now(), 'password' => bcrypt('password'), // Or use Hash::make('password') 'remember_token' => Str::random(10), ]; } }
For the Post
model, update the factory database/factories/PostFactory.php
to generate posts with a title, content, and a user_id to associate each post with a user. Here's how the PostFactory
might look:
<?php namespace Database\Factories; use Illuminate\Database\Eloquent\Factories\Factory; use App\Models\Post; use App\Models\User; class PostFactory extends Factory { /** * The name of the factory's corresponding model. * * @var string */ protected $model = Post::class; /** * Define the model's default state. * * @return array */ public function definition() { return [ 'title' => $this->faker->sentence(), 'content' => $this->faker->paragraph(), 'user_id' => User::factory(), // Automatically creates a User for each Post ]; } }
To use these factories, you can now create below seeders and use the factories within them to populate your database.
php artisan make:seeder UsersTableSeeder php artisan make:seeder PostsTableSeeder
For instance, in our UsersTableSeeder.php
, we will add:
<?php namespace Database\Seeders; use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; use App\Models\User; class UsersTableSeeder extends Seeder { /** * Run the database seeds. */ public function run(): void { User::factory()->count(10)->create(); } }
And similarly, in our PostsTableSeeder.php
<?php namespace Database\Seeders; use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; use App\Models\Post; class PostsTableSeeder extends Seeder { /** * Run the database seeds. */ public function run(): void { Post::factory()->count(50)->create(); } }
Next we need to call these seeders in your database/seeders/DatabaseSeeder.php
<?php namespace Database\Seeders; // use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder { /** * Seed the application's database. */ public function run(): void { // Call other seeders here $this->call([ UsersTableSeeder::class, PostsTableSeeder::class, ]); } }
Next execute below command to populate the database with users and posts. This setup will give you a good amount of dummy data to work with while developing and testing your GraphQL API.
php artisan db:seed
To view the content of your database, use Laravel Tinker:
php artisan tinker
Example commands in Tinker:
\App\Models\User::all();
\App\Models\Post::first();
Sample Output:
8. Setting Up GraphQL Routes
The rebing/graphql-laravel
package is an invaluable tool that lets you define GraphQL endpoints in your Laravel app. You’ll be sending your GraphQL queries and mutations through these routes, so it’s useful to get the hang of them early on.
Typically, you’ll want to define these routes in either your routes/web.php
or routes/api.php
file. If you opt for the latter option, make sure to prefix them with /api
.
Here's a guide on how to define the GraphQL routes for your queries and mutations. Place it in routes/api.php
:
use Illuminate\Support\Facades\Route;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\GraphQLController;
Route::group(['prefix' => 'graphql'], function () {
Route::post('/', [GraphQLController::class, 'query'])->name('graphql.execute');
Route::get('/', [GraphQLController::class, 'query'])->name('graphql.execute');
});
In this setup, both GET and POST requests are directed to the same method (query
). This is because, typically, a GraphQL server uses the same endpoint and method to handle all requests, distinguishing between queries and mutations based on the request payload, not the HTTP method.
9. Running the Laravel Server
To test your API with Postman Start your Laravel development server:
php artisan serve
10. Testing GraphQL API
To test your GraphQL API with Postman, follow the steps below using this walk through as a reference:
POST Request
- Update the request type to POST: In Postman, create a new request and change its type to POST.
- Put in the URL: Use the URL of your GraphQL endpoint (e.g.,
http://localhost:8000/graphql
if you're running your Laravel app locally). - Set Headers: Add a header and set
Content-Type
toapplication/json
. - Configure the Body: Select 'Body' tab, then pick 'raw', and finally select 'JSON' as the format. Write your GraphQL query or mutation in the body as shown below for example’s sake:
GET Request
Testing with a GET request is quite different because you have to pass the query as a URL parameter:
- Change the type of request to GET: Create a new request in Postman and switch the type to GET.
- Fill in the URL with Query Parameters: To append your query directly to the URL, you will have to encode it. For example:
http://localhost:8000/graphql?query={users{id,name,email,posts{id,title}}}
So with this we have successfully built and tested Laravel GraphQL API Server by sending POST and GET Requests.