Laravel GraphQL: Building APIs with GraphQL and Laravel
Application Programming Interfaces (APIs) are a crucial element of modern web development. They allow different applications and services to communicate with each other, sharing data and functionality, which enables developers to create complex, interconnected systems that would be impossible with standalone applications.
One of the most popular and powerful API technologies today is GraphQL. Unlike traditional REST APIs, which have fixed endpoints and return predefined data structures, GraphQL APIs allow clients to precisely define the data they need and receive it in a predictable and hierarchical format. This allows for more flexible and efficient data fetching and eliminates the need for over-fetching or under-fetching data.
We will learn how to create a GraphQL API with Laravel, a popular PHP web framework. We will be creating a simple student model, seeding the database with dummy data, setting up a database connection, and creating a GraphQL server by defining our API's schema, queries, and mutations. We’ll also learn how to make requests to our API (test our endpoints) using a tool like Insomnia or Postman. By the end of this blog, you will have a working GraphQL API that you can continue to expand and improve.
Prerequisites
To create a GraphQL API with Laravel, you will need to have a basic understanding of the Laravel framework and the GraphQL query language.
Additionally, make sure to have the following installed on your machine:
- PHP 7.4 or higher
- Composer
- MySQL
- Laravel 7.0 or higher
Step 1: Setting up the Laravel project
Open your terminal and navigate to the directory where you want to create your project. Then, run the following command to create a new Laravel project:
composer create-project --prefer-dist laravel/laravel graphql-tutorial
This will create a new Laravel project in a directory called graphql-tutorial.
Step 2: Setting up the database
Next, we need to set up a database connection. Open the .env file in the root of your project and update the following lines with your database credentials:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=YOUR_DATABASE_NAME
DB_USERNAME=YOUR_DATABASE_USERNAME
DB_PASSWORD=YOUR_DATABASE_PASSWORD
Step 3: Creating the student model
Next, we will create a student model and a corresponding migration file to create a students table in the database. Run the following command to create the model and the migration file:
php artisan make:model Student -m
This will create a Student model and a migration file in the database/migrations directory. Open the migration file and add the following columns to the students table:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateStudentsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('students', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email');
$table->integer('age');
$table->string('country');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('students');
}
}
Next, run the following command to create the students table in the database:
php artisan migrate
You’ll also want to add these fields to the Student model in app/Student.php:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Student extends Model
{
protected $fillable = ["name", "email", "age", "country"];
}
Step 4: Creating a seeder file
Next, we will create a seeder file to seed the students table with dummy data:
php artisan make:seeder StudentTableSeeder
This will create a seeder file called StudentTableSeeder.php in the database/seeds directory.
Open the seeder file and add the following code:
<?php
use Illuminate\Database\Seeder;
use App\Student;
class StudentTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
// Create 10 students
factory(Student::class, 10)->create();
}
}
This code uses the Laravel factory to create 10 students and insert them into the students table. Next, open the DatabaseSeeder.php file and add the following line to the run() method:
$this->call(StudentTableSeeder::class);
This will run the StudentTableSeeder when you run the db:seed command. Finally, run the following command to seed the students table with dummy data:
php artisan db:seed
Step 5: Installing the rebing/graphql-laravel package
Next, we will install the rebing/graphql-laravel package, which will allow us to easily create a GraphQL server in Laravel.
composer require rebing/graphql-laravel
This will add the necessary dependencies to your Laravel project. Once the package is installed, run the following command to publish the GraphQL configuration file:
php artisan vendor:publish --provider="Rebing\GraphQL\GraphQLServiceProvider"
Step 6: Creating the GraphQL server
Next, we will create the GraphQL server. First, create a graphql directory in the routes directory. Then, create a file called StudentType.php in the routes/graphql directory and add the following code:
<?php
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Type as GraphQLType;
class StudentType extends GraphQLType
{
protected $attributes = [
'name' => 'Student',
'description' => 'A student',
'model' => \App\Student::class
];
public function fields(): array
{
return [
'id' => [
'type' => Type::nonNull(Type::int()),
'description' => 'The id of the student'
],
'name' => [
'type' => Type::string(),
'description' => 'The name of the student'
],
'email' => [
'type' => Type::string(),
'description' => 'The email of the student'
],
'age' => [
'type' => Type::int(),
'description' => 'The age of the student'
],
'country' => [
'type' => Type::string(),
'description' => 'The country of the student'
],
'created_at' => [
'type' => Type::string(),
'description' => 'The date the student was created',
'resolve' => function($model) {
return $model->created_at;
}
],
'updated_at' => [
'type' => Type::string(),
'description' => 'The date the student was last updated',
'resolve' => function($model) {
return $model->updated_at;
}
]
];
}
}
The code above creates a GraphQL type called StudentType that represents a student in the database. It defines fields for the id, name, email, age, country, created_at, and updated_at columns of the students table.
In the fields() function, you’ll notice each field in the StudentType has type and description parameters. Fields in a GraphQL type require a type and description because these parameters provide important information for the GraphQL server to understand and validate the data being requested or provided by the client. The type specifies the data type of the field, such as a string or integer, and the description provides additional information about the field, such as its purpose or expected format. This information is used by the GraphQL server to properly handle and respond to client queries and mutations, as well as by various tools, such as GraphQL clients and IDEs, to generate better documentation and support for developers.
Now, create a file called StudentsQuery.php in the routes/graphql directory. Open the file and add the following code:
<?php
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Query;
class StudentsQuery extends Query
{
protected $attributes = [
'name' => 'students'
];
public function type(): Type
{
return Type::listOf(GraphQL::type('student'));
}
public function args(): array
{
return [
'id' => [
'name' => 'id',
'type' => Type::int()
],
'name' => [
'name' => 'name',
'type' => Type::string()
],
'email' => [
'name' => 'email',
'type' => Type::string()
],
'age' => [
'name' => 'age',
'type' => Type::int()
],
'country' => [
'name' => 'country',
'type' => Type::string()
]
];
}
public function resolve($root, $args)
{
if (isset($args['id'])) {
return Student::where('id', $args['id'])->get();
}
if (isset($args['name'])) {
return Student::where('name', $args['name'])->get();
}
if (isset($args['email'])) {
return Student::where('email', $args['email'])->get();
}
if (isset($args['age'])) {
return Student::where('age', $args['age'])->get();
}
if (isset($args['country'])) {
return Student::where('country', $args['country'])->get();
}
return Student::all();
}
}
The code we have above defines a GraphQL query field for a list of students. The protected $attributes array sets the field name as "students". Thus, when a client application makes a query, it will use this name to request the list of students. The public function type() returns the type of field. In this case, it is using the helper function Type::listOf() to indicate that the field is a list of items. The argument passed to the function, GraphQL::type('student'), specifies that each item in the list is of the Student type. Therefore, when the client makes a query for this field, the server will know that it is expecting a list of student objects and will retrieve and return the appropriate data to the client.
The public function args() returns an array of input fields for the query. We’ll use these fields to retrieve data from our GraphQL server. The name parameter for each field specifies how data from this field will be retrieved on the client side, and the type parameter specifies the type of data that will be retrieved from this field.
The resolve function in a GraphQL query is responsible for fetching data from the server based on the input arguments. The function takes in two parameters, $root and $args. $root refers to the root object of the query, and $args refers to the input arguments passed to the query.
In the function, we start by checking whether the id argument is passed in the query. If it is, we search for the id in our database using Eloquent's where method, get the result using the get method, and return it to our client. When the name argument is passed, we return all the students with the given name by using Eloquent's where method and getting the result by using the get method. The same logic applies to the email, age, and country arguments. If none of these arguments are passed, it returns all the students by using the all method.
Mutation
You should know this already, but a GraphQL mutation is a type of operation that allows a client to modify data on the server. It is similar to a POST, PUT, or PATCH request in REST. Here are the mutations that our server needs:
- Create: Allows the client to create a new student on the server.
- Update: Allows the client to update an existing student on the server.
- Delete: Allows the client to delete a student on the server.
For our first mutation, create a file called CreateStudentMutation.php in the routes directory and add the following code:
<?php
use App\Student;
use Rebing\GraphQL\Support\Facades\GraphQL;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Mutation;
class CreateStudentMutation extends Mutation
{
protected $attributes = [
'name' => 'createStudent'
];
public function type(): Type
{
return GraphQL::type('student');
}
public function args(): array
{
return [
"id" => [
'name' => 'id',
'type' => Type::nonNull(Type::int()),
],
'name' => [
'name' => 'name',
'type' => Type::nonNull(Type::string())
],
'email' => [
'name' => 'email',
'type' => Type::nonNull(Type::string())
],
'age' => [
'name' => 'age',
'type' => Type::nonNull(Type::int())
],
'country' => [
'name' => 'country',
'type' => Type::nonNull(Type::string())
]
];
}
public function resolve($root, $args)
{
$student = new Student();
$student->name = $args['name'];
$student->email = $args['email'];
$student->age = $args['age'];
$student->country = $args['country'];
$student->save();
return $student;
}
}
Above, we created a GraphQL mutation called createStudent that allows clients to create a new student in our database. It takes the name, email, age, and country of the student as arguments.
The protected $attributes array specifies how a client utilizes the mutation. The public function type() returns the type of mutation, the student object.
The args() function returns an array of arguments. These fields will be created for each mutation entry. Each entry has a name (the name of the database column) and a (data) type like int and String. The Type::nonNull constraint ensures that null values are not entered into the database.
The resolve function does the final work of entering the values provided into the database. In this function, we create an instance of the Student model, save the values for name, email, age, and country in some variables, and save the values to our database with $student->save().
Next, to update student entries in our database, create a file called UpdateStudentMutation.php in the routes directory and add the following code:
<?php
namespace App\graphql\Mutations;
use App\Student;
use Rebing\GraphQL\Support\Facades\GraphQL;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Mutation;
class UpdateStudentMutation extends Mutation
{
protected $attributes = [
"name" => "updateStudent",
];
public function type(): Type
{
return GraphQL::type("Student");
}
public function args(): array
{
return [
'name' => [
'name' => 'name',
'type' => Type::nonNull(Type::string())
],
'email' => [
'name' => 'email',
'type' => Type::nonNull(Type::string())
],
'age' => [
'name' => 'age',
'type' => Type::nonNull(Type::int())
],
'country' => [
'name' => 'country',
'type' => Type::nonNull(Type::string())
]
];
}
public function resolve($root, $args)
{
$student = Student::findOrFail($args["id"]);
$student->fill($args);
$student->save();
return $student;
}
}
The code for CreateStudentMutation is very similar to the code for UpdateStudentMutation. The only difference is in the resolve function, where we search for the specific student to update. With Student::findOrFail($args["id"]), we take the student id supplied by a client application and search for it in the database. With the fill function, we replace all of the data in the row of the id supplied with the new data provided. Then, we save the new data in the database.
To delete a student from the database, create a file called DeleteStudentMutation.php in the routes directory and add the following code:
<?php
namespace App\graphql\Mutations;
use App\Student;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Mutation;
class DeleteStudentMutation extends Mutation
{
protected $attributes = [
"name" => "deleteStudent",
"description" => "Delete a student",
];
public function type(): Type
{
return Type::boolean();
}
public function args(): array
{
return [
"id" => [
"name" => "id",
"type" => Type::int(),
"rules" => ["required"],
],
];
}
public function resolve($root, $args)
{
$student = Student::findOrFail($args["id"]);
return $student->delete() ? true : false;
}
}
To delete a student from our database, all we need is an id, which we get from a client application. "rules" => ["required"] ensures that an id is provided. With findOrFail, we search for the id in our database, and the delete() function deletes all of the data with that id.
Registering our query, mutations, and type
The last step is to register the type, query, and mutations that we just created in the GraphQL configuration file, config/graphql.php.
return [
...
'schemas' => [
'default' => [
'query' => [
'students' => App\GraphQL\Queries\StudentsQuery::class,
],
'mutation' => [
// Create a student
'createStudent' => App\GraphQL\Mutations\CreateStudentMutation::class,
// update student
'updateStudent' => App\GraphQL\Mutations\UpdateStudentMutation::class,
// delete a student
'deleteStudent' => App\GraphQL\Mutations\DeleteStudentMutation::class,
],
'middleware' => [],
'method' => ['get', 'post'],
],
],
'types' => [
'Student' => App\GraphQL\Types\StudentType::class,
],
...
]
Step 7: Testing the endpoints
Now that the GraphQL server is set up, we can test the endpoints. Open your browser and navigate to http://localhost:8000/graphql. You should see the GraphQL Playground interface. In the left-hand panel, type the following query to fetch all students:
{
students {
id
name
age
country
}
}
Query Students
Then, click the "Play" button to execute the query. You should see a list of students in the right-hand panel.
To test creating a student with a mutation, you can use the following mutation:
mutation {
createStudent(name: "New Student", email: "newstudent@example.com", age: 20, country: "Canada") {
id
name
age
country
}
}
Create a Student
To test updating a student with a mutation, you can use the following mutation:
mutation {
updateStudent(id: 1, name: "Updated Student", email: "updatedstudent@example.com", age: 21, country: "USA") {
id
name
age
country
}
}
Update a Student
You can execute these mutations in the same way as the queries, by typing the mutation in the left-hand panel and clicking the "Play" button.
To delete a student from our database, we use the following mutation:
mutation {
deleteStudent(id : 1)
}
Delete a Student
Conclusion
In this article, we’ve learned how to create a GraphQL API using Laravel and the rebing/graphql-laravel package. We’ve also created a Student model, seeded the database with dummy data, set up a database connection, created the GraphQL server, and tested the endpoints. Now, you can create your own GraphQL APIs with Laravel and the graphql-laravel package.