Forget REST. Here's How to Make a GraphQL API with Laravel

Forget REST. Here's How to Make a GraphQL API with Laravel

ยท

5 min read

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.

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 and DateTime. GraphQL's default scalar types are Int, Float, String, Boolean, and ID.
  • Near the top, we define the root Query type. In it, we'll add the query for finding the user, which both Item and Sale 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.
ย