GraphQL, in my opinion, has been one of the best technologies to be released in the last ten years. GraphQL is a specification that describes how we can use just one query to get data from an API instead of hitting multiple endpoints with different HTTP request methods as seen with REST APIs. Because it's just a specification, GraphQL can simply be implemented in any programming language or framework. In my opinion, one of the best libraries for Laravel based on a PHP GraphQL implementation is Lighthouse.
Preliminaries:
- In your terminal, navigate to your Laravel project and install Lighthouse by typing
composer require nuwave/lighthouse
. - Next, run
php artisan vendor:publish --tag=lighthouse-schema
. This will create the schema in our new graphql folder. - If you want type-checking and autocompletion in your IDE, run
php artisan lighthouse:ide-helper
and it will create three other files in your root: _lighthouse_ide_helper.php, programmatic-types.graphql, and schema-directives.graphql.- NB: If you're using git, please add these files to your .gitignore file.
- Finally, no GraphQL project is complete without a GUI. Lighthouse recommends that we use the GraphQL Playground library for Laravel:
composer require mll-lab/laravel-graphql-playground
.- We'll be able to access it in our browser with
/graphql-playground
. - Personally, I would've preferred if there was a GraphiQL library for Laravel that was recently updated. GraphiQL is what I use for my Gatsby projects.
- We'll be able to access it in our browser with
First Steps: Before we get to work, we'll need to set up our database and do some seeding. Let's create a tiny and simple e-commerce platform where a seller can only create one item at a time. We'll also make each item have multiple sales.
- Each user will have one
Item
at a time. We'll also be type-hinting our model so we can have an easier time creating our GraphQL schema.
<?php
...
class CreateItemTable extends Migration
{
public function up()
{
Schema::create('item', function (Blueprint $table) {
$table->id();
$table->string('name')->nullable(false);
$table->dateTimeTz('expiry_time')->nullable(false);
$table->string('description', 140)->nullable(false);
$table->float('price')->nullable(false);
$table->bigInteger('seller_id')->nullable(false)->unsigned();
$table->timestamps();
});
}
...
}
<?php
...
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Item extends Model
{
use HasFactory;
protected $table = 'item';
public function sales(): HasMany
{
return $this->hasMany('App\Models\Sale');
}
public function seller(): BelongsTo
{
return $this->belongsTo('App\Models\User', 'seller_id');
}
}
- Next, we'll create the Sales migration and model with similar type-hinting.
<?php
...
class CreateSalesTable extends Migration
{
public function up()
{
Schema::create('sales', function (Blueprint $table) {
$table->id();
$table->integer('quantity')->nullable(false);
$table->float('amount')->nullable(false);
$table->bigInteger('item_id')->nullable(false)->unsigned();
$table->bigInteger('seller_id')->nullable(false)->unsigned();
$table->timestamps();
});
}
...
}
<?php
...
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Sale extends Model
{
use HasFactory;
public function item(): BelongsTo
{
return $this->belongsTo('App\Models\Item');
}
public function seller(): BelongsTo
{
return $this->belongsTo('App\Models\User', 'seller_id');
}
}
- Finally, we can define our User model.
<?php
...
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\HasMany;
class User extends Authenticatable
{
...
public function item(): HasOne
{
return $this->hasOne('App\Models\Item', 'seller_id');
}
public function sales(): HasMany
{
return $this->hasMany('App\Models\Sale', 'seller_id');
}
}
- Now we'll need to seed our tables (if you don't want to enter data manually). I don't have time to go through Seeders and Factories here so please check out the docs.
- We can now test our GraphQL configuration. Open GraphQL Playground and let's retrieve the first user in our database:
{ user(id: 1){ name email } }
The Work Begins: Here we will be defining our schema. It's important that you have some grasp of the types used in a GraphQL schema. We'll only brush up on this, so for a deeper understanding, I'd advise you to check out GraphQL's docs on the subject.
- Open the graphql/schema.graphql file and you will see the definitions for two custom scalar types,
Date
andDateTime
. GraphQL's default scalar types areInt
,Float
,String
,Boolean
, andID
. - Near the top, we define the root
Query
type. In it, we'll add the query for finding the user, which bothItem
andSale
models are attached to.- NB: Our Laravel models are 'Object Types' in GraphQL. Again, please remember to visit the GraphQL docs for a deeper understanding.
type Query {
user(id: ID @eq): User @find
}
The Work Continues:
- Now we'll define our object types by giving them fields and defining each field with types. Let's start with the User object type.
type User {
id: ID!
name: String!
email: String!
item: Item @hasOne
sales: [Sale] @hasMany
created_at: DateTime!
updated_at: DateTime!
}
- A few notes about the code above:
- An exclamation point simply means that the field can't be null. An error will be returned if the query returns a null value.
- When a type is between square brackets, that means we can expect a List.
- Now let's define the Item object type:
type Item {
id: ID!
name: String!
expiry_time: DateTime!
description: String!
price: Float!
sales: [Sale] @hasMany
seller: User! @belongsTo
created_at: DateTime!
updated_at: DateTime!
}
- See how easy it is? Now you should be able to do the
Sale
object type on your own.
The Work Ends:
- Go to GraphQL Playground and run a query. Here's one that should work wonderfully if the database is seeded correctly and relationships are properly defined.
{ user(id: 1){ name email item { name price description expiry_time sales { quantity amount } } } }
- For any troubleshooting, read the error message on the right of the screen. Most times it will tell you exactly what you need.
Conclusion:
- There are so many more things you can do with GraphQL queries. One of the most important things we can do is authentication, which we'll discuss in another post. We will also discuss mutations in the future.
- If you want to get ahead on your own with Lighthouse, please refer to the Lighthouse docs.
- I prefer Lighthouse because of its ease-of-use and the ability to edit the schema directly. However, if you want to make a GraphQL API the PHP/Laravel way, then I recommend you check out Rebing's library.