laravel graphql

How To Build GraphQL APIs Using Laravel

Spread the love

Last Updated on February 16, 2023

Laravel provides a clean interface for building REST APIs. Rest APIs have been the defacto way of building flexible and scalable APIs. This though comes at a cost because as the API grows, so is the payload returned by the APIs. This leads to a drop in performance especially if your application has many resources. This is where GraphQL comes in.

Graphql is a query language and runtime for APIs that was developed by Facebook. It provides a more efficient and flexible alternative to traditional REST APIs by allowing the client to specify exactly what data it needs, rather than having the server return a fixed set of data. This results in faster and more efficient API calls, making it an ideal choice for modern web development.

Platforms such as Shopify have built their APIs using both GraphQl and REST which allow developers to choose which type of their API they will connect to.

In this tutorial, we will explore the benefits of using Graphql and how to set it up for your next laravel project.

We will cover everything from installing a Graphql package to building a simple Graphql API and exploring advanced topics such as authentication and pagination.

Whether you’re a beginner or an experienced Laravel developer, this tutorial will provide you with the knowledge you need to get started with Laravel Graphql. So let’s dive in!

Key Concepts of GraphQL.

There are some key concepts of Graphql we will need to learn before we can dive into the tutorial.

Schema

A graphql schema defines the structure of the graphql. Similar to a database schema, it provides a blueprint on how the Graphql API should be queried.

Query

Similar to a database query, a Graphql query is used to fetch data from the API. They allow clients to request specific data from the server

Mutations

Mutations allow a client to send data to the server which can be created, updated or deleted.

Types

Graphql uses a type system to define the datatypes of data that can be queried. The types are the default data types we know(Integer, String, Float etc).

Resolvers

Resolvers are functions that are used to retrieve data from the server and return it to the client. They can also be used in mutations to add/update data in the database.

Arguments

These are additional data that can be passed together with the query fields to further customize the data returned by the API. Examples of arguments are limits, and page numbers when fetching paginated results.

Subscriptions

Subscriptions allow clients to receive real-time data from the server when data changes.

Query Batching

Graphql allows multiple queries to be executed in a single request thus reducing the number of round trips between the server and client

These are some of the key concepts of Graphql that are useful when building or consuming a Graphql API. You can find more concepts on the Graphql website.

How to Set up a GraphQL Server in Laravel

In order to build a graphql API in Laravel, there are a few key things we need to install and configure.

Install a Graphql package

In this tutorial, I will use rebing/graphql-laravel package but there are other packages such as the lighthouse-php package to add a graphql server. They both come with graphiql playground which allows us to query our graphql API.

composer require rebing/graphql-laravel

Configure the Package

We can publish the configurations

php artisan vendor:publish --provider="Rebing\GraphQL\GraphQLServiceProvider"

This will publish a config/graphql.php file that we will use to register our queries, mutations and schemas.

Building a simple GraphQL API.

In this part, we will focus on creating the queries, schemas, mutations and testing the API through the graphiql playground interface. I will use a simple example of Users and Articles which involves a one-to-many relationship.

GraphQL APIs normally contain a single endpoint(/graphql) that is accessible through a POST request. This endpoint is responsible for manipulating all data; be it adding data, fetching data or even deleting data. Through Schemas, Queries and Mutations this is made possible. Let’s get started building a simple GraphQL API in Laravel.

Create Migrations and Seeders.

We can start by creating the migrations and seeding the database. Let’s start by creating the Article’s Model, factory and migration file.

php artisan make:model Articles -m -f

The -m flag generates a migration file while the -f flag generates the corresponding Factory class that we will use to seed the database.

//App/Models/Articles.php

<?php

namespace App\Models;

use App\Models\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class Articles extends Model
{
    use HasFactory;

    protected $fillable = [
        'title', 'author_id', 'content'
    ];

    public function author()
    {
        return $this->belongsTo(User::class, 'author_id');
    }
}

Articles Model

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('articles', function (Blueprint $table) {
            $table->id();

            $table->unsignedBigInteger('author_id')->index();
            $table->string('title');
            $table->longText('content');
            $table->timestamps();

            $table->foreign('author_id')->references('id')->on('users')->cascadeOnDelete();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('articles');
    }
};

Migration File

<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\DB;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Articles>
 */
class ArticlesFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition()
    {
        $userIDS = DB::table('users')->pluck('id');

        return [
            'title' => $this->faker->words(2, true),
            'content' => $this->faker->sentences(3, true),
            'author_id' => $this->faker->randomElement($userIDS)
        ];
    }
}

Article’s Factory

We can then migrate and seed the database

php artisan migrate --seed

Create a Type

Each “Resource” present in the system must have a type definition. This is used by graphql to define the structure of the queries.

To create a type, we can run the following command provided by the package

php artisan make:graphql:type ArticleType

Here, we can define the fields that can be accessed on the client side. Think of them as “API Resources” but on steroids.

//App/GraphQL/Types;

<?php

declare(strict_types=1);

namespace App\GraphQL\Types;

use App\Models\Articles;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Type as GraphQLType;

class ArticlesType extends GraphQLType
{
    protected $attributes = [
        'name' => 'Articles',
        'description' => 'A type definition for the Articles Model',
        'model' => Articles::class
    ];

    public function fields(): array
    {
        return [
            'id' => [
                'type' => Type::nonNull(Type::id()),
                'description' => 'The auto incremented Article ID'
            ],
            'title' => [
                'type' => Type::nonNull(Type::string()),
                'description' => 'A title of the Article'
            ],
            'content' => [
                'type' => Type::nonNull(Type::string()),
                'description' => 'The Body of the Article'
            ],
            'author' => [
                'type' => Type::nonNull(GraphQL::type('User')),
                'description' => 'The Author who wrote the articles'
            ],
        ];
    }
}

Once the type definitions are created, we can use them in the subsequent queries and mutations.

Define the Queries

Now that we have data in the database, we can now prepare the queries. As we saw earlier, Queries are useful for fetching data from the API.

To create a Query we can run the following command.

php artisan make:graphql:query Articles/ArticleQuery

This query will be responsible for returning a single Article.

We will use the id as an argument and pass it to the resolver which will return the Single Article

//App/GraphQL/Queries/Articles/ArticleQuery
<?php

declare(strict_types=1);

namespace App\GraphQL\Queries\Articles;

use App\Models\Articles;
use Closure;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Query;
use Rebing\GraphQL\Support\SelectFields;

class ArticleQuery extends Query
{
    protected $attributes = [
        'name' => 'article',
        'description' => 'A query for fetching a single article'
    ];

    public function type(): Type
    {
        return GraphQL::type('Articles');
    }

    public function args(): array
    {
        return [
            'id' => [
                'name' => 'id',
                'type' => Type::nonNull(Type::id())
            ]
        ];
    }

    public function resolve($root, array $args, $context, ResolveInfo $resolveInfo, Closure $getSelectFields)
    {
        /** @var SelectFields $fields */
        $fields = $getSelectFields();
        $select = $fields->getSelect();
        $with = $fields->getRelations();

        return Articles::select($select)->with($with)->findOrFail($args['id']);
    }
}

From the code, we can see that we are using the default type we created earlier to structure the queries.

I will also create a new query that will return all/paginated articles

php artisan make:graphql:query Articles/ArticlesQuery

We can then add the following logic to return the Paginated Responses.

//App/GraphQL/Queries/Articles/ArticlesQuery

<?php

declare(strict_types=1);

namespace App\GraphQL\Queries\Articles;


use App\Models\Articles;
use Closure;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Query;
use Rebing\GraphQL\Support\SelectFields;

class ArticlesQuery extends Query
{
    protected $attributes = [
        'name' => 'articles',
        'description' => 'A query to fetch all articles'
    ];

    public function type(): Type
    {
        // return Type::listOf(GraphQL::type('Articles')); //return all results
        return GraphQL::paginate('Articles'); //return paginated results
    }

    public function args(): array
    {
        return [
            'limit' => [
                'type' => Type::int()
            ],
            'page' => [
                'type' => Type::int()
            ]
        ];
    }

    public function resolve($root, array $args, $context, ResolveInfo $resolveInfo, Closure $getSelectFields)
    {
        /** @var SelectFields $fields */
        $fields = $getSelectFields();
        $select = $fields->getSelect();
        $with = $fields->getRelations();

        if (!array_key_exists('limit', $args) || !array_key_exists('page', $args)) {
            $articles = Articles::with($with)->select($select)
                ->paginate(5, ['*'], 'page', 1);
        } else {
            $articles = Articles::with($with)->select($select)
                ->paginate($args['limit'], ['*'], 'page', $args['page']);
        }

        return $articles;

        // return Articles::select($select)->with($with)->get(); This returns all records without pagination
    }
}

Similar to the single article query, we also use the ArticleType we created earlier to format the paginated results.

With that, the queries are done.

Define Mutations.

Mutations are useful when we want to create, update or delete records in the database.

Create Articles Mutation

This mutation will be responsible for adding an article record to the database.

We will also use another command provided by the package

php artisan make:graphql:mutation Articles/CreateArticleMutation
//App/GraphQL/Mutations/Articles/CreateArticleMutation

<?php

declare(strict_types=1);

namespace App\GraphQL\Mutations\Articles;

use App\Models\Articles;
use Closure;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Mutation;


class CreateArticleMutation extends Mutation
{
    protected $attributes = [
        'name' => 'CreateArticle',
        'description' => 'A mutation for Creating an Article'
    ];

    public function type(): Type
    {
        return GraphQL::type('Articles');
    }

    public function args(): array
    {
        return [
            'title' => [
                'type' => Type::nonNull(Type::string()),
                'description' => 'A title of the Article'
            ],
            'content' => [
                'type' => Type::nonNull(Type::string()),
                'description' => 'The Body of the Article'
            ],
            'author_id' => [
                'type' => Type::nonNull(Type::id()),
                'description' => 'The Author of the Article'
            ]
        ];
    }

    public function resolve($root, array $args, $context, ResolveInfo $resolveInfo, Closure $getSelectFields)
    {
        return Articles::create($args);
    }
}

Update Articles Mutation

We can also update articles in the database

php artisan make:graphql:mutation Articles/UpdateArticleMutation
//App/GraphQL/Mutations/Articles/UpdateArticleMutation

<?php

declare(strict_types=1);

namespace App\GraphQL\Mutations\Articles;

use App\Models\Articles;
use Closure;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Mutation;


class UpdateArticleMutation extends Mutation
{
    protected $attributes = [
        'name' => 'UpdateArticle',
        'description' => 'A mutation for Updating an Article'
    ];

    public function type(): Type
    {
        return GraphQL::type('Articles');
    }

    public function args(): array
    {
        return [
            'id' => [
                'type' => Type::nonNull(Type::id()),
                'description' => 'The auto incremented Article ID'
            ],
            'title' => [
                'type' => Type::nonNull(Type::string()),
                'description' => 'A title of the Article'
            ],
            'content' => [
                'type' => Type::nonNull(Type::string()),
                'description' => 'The Body of the Article'
            ],
            'author_id' => [
                'type' => Type::nonNull(Type::id()),
                'description' => 'The Author of the Article'
            ]
        ];
    }

    public function resolve($root, array $args, $context, ResolveInfo $resolveInfo, Closure $getSelectFields)
    {

        $article = Articles::findOrFail($args['id']);

        $article->update($args);

        return $article;
    }
}

We are using the article id as an argument and passing it to the resolver which returns a single article which we can update its details.

Delete Articles Mutation

Finally, we need to prepare a delete mutation to finish the CRUD operations

php artisan make:graphql:mutation Articles/DeleteArticlesMutation
//App/GraphQL/Mutations/Articles/DeleteArticleMutation

<?php

declare(strict_types=1);

namespace App\GraphQL\Mutations\Articles;

use App\Models\Articles;
use Closure;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Mutation;


class DeleteArticleMutation extends Mutation
{
    protected $attributes = [
        'name' => 'DeleteArticle',
        'description' => 'A mutation for deleting an Article'
    ];

    public function type(): Type
    {
        return Type::boolean();
    }

    public function args(): array
    {
        return [
            'id' => [
                'type' => Type::nonNull(Type::id()),
                'description' => 'The auto incremented Article ID'
            ],
        ];
    }

    public function resolve($root, array $args, $context, ResolveInfo $resolveInfo, Closure $getSelectFields)
    {
        $article = Articles::findOrFail($args['id']);

        return $article->delete();
    }
}

Similar to the Update Mutation, we are using the article’s id to resolve the article and later delete it.

When we delete a record, we normally return a 204(No Content) status code in REST APIs. However, this is not possible in GraphQL APIs and as a result, we need to return a boolean so that we know if the mutation was successful. Upon deletion, the response will return true.

Now that all the mutations and queries have been created, we can register them in the config/graphql.php file

//config/graphql.php

<?php

...

   'schemas' => [
        'default' => [
            'query' => [
                // Article Queries
                App\GraphQL\Queries\Articles\ArticleQuery::class,
                App\GraphQL\Queries\Articles\ArticlesQuery::class,

                // User Queries
                App\GraphQL\Queries\Users\UsersQuery::class,
                App\GraphQL\Queries\Users\UserQuery::class,
            ],
            'mutation' => [
                // Article Mutations
                App\GraphQL\Mutations\Articles\CreateArticleMutation::class,
                App\GraphQL\Mutations\Articles\UpdateArticleMutation::class,
                App\GraphQL\Mutations\Articles\DeleteArticleMutation::class,

                // User Mutations
                App\GraphQL\Mutations\Users\AddUserMutation::class,
                App\GraphQL\Mutations\Users\UpdateUserMutation::class,
                App\GraphQL\Mutations\Users\DeleteUserMutation::class,
                App\GraphQL\Mutations\Users\LoginMutation::class
            ],
            // The types only available in this schema
            'types' => [
                App\GraphQL\Types\ArticlesType::class,
                App\GraphQL\Types\UserType::class,
            ],

            // Laravel HTTP middleware
            'middleware' => null,

            // Which HTTP methods to support; must be given in UPPERCASE!
            'method' => ['GET', 'POST'],

            // An array of middlewares, overrides the global ones
            'execution_middleware' => null,
        ],
    ],

You can run the command artisan optimize to recache the configurations

Working with Authentication

As we know, authentication and authorization are key features for any API and Graphql APIs are no exceptions either. We can issue access tokens through Sanctum or Passport and use them to authorize user requests.

The package provides an authorize() method in every GraphQL Query or Mutation which we can override and add our custom logic to authorize requests.

We can create a UserLoginMutation that will issue a token.

php artisan make:graphql:mutation Users/LoginMutation
//App/GraphQL/Mutations/Users/LoginMutation

<?php

declare(strict_types=1);

namespace App\GraphQL\Mutations\Users;

use App\Models\User;
use Closure;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use Illuminate\Support\Facades\Auth;
use Rebing\GraphQL\Support\Mutation;

class LoginMutation extends Mutation
{
    protected $attributes = [
        'name' => 'Login',
        'description' => 'A mutation for login a user'
    ];

    public function type(): Type
    {
        return Type::string();
    }

    public function args(): array
    {
        return [
            'email' => [
                'type' => Type::nonNull(Type::string()),
                'description' => 'The email of the user',
                'rules' => ['max:30']
            ],
            'password' => [
                'name' => 'password',
                'type' => Type::nonNull(Type::string()),
                'description' => 'The password of the user',
            ]
        ];
    }

    public function resolve($root, array $args, $context, ResolveInfo $resolveInfo, Closure $getSelectFields)
    {
        $credentials = [
            'email'=>$args['email'],
            'password'=>$args['password']
        ];
        if (!Auth::attempt($credentials)) {
            return 'Invalid login details';
        }

        $user = User::where('email', $args['email'])->firstOrFail();

        $token = $user->createToken('auth_token')->plainTextToken;

        return $token;
    }
}

We can then add the token in the headers that we will then check in the authorize method and return the records if the user is authorized or return a default message if the user is not authorized

//App/GraphQL/Queries/Articles/ArticlesQuery
<?php

...

public function authorize($root, array $args, $ctx, ?ResolveInfo $resolveInfo = null, ?Closure $getSelectFields = null): bool
    {
        if (request()->user('sanctum')) {
            return true;
        } else
            return false;
    }

    public function getAuthorizationMessage(): string
    {
        return "You are not authorized to perform this action";
    }

With that, we have created an authenticated GraphQL API in Laravel.

How is Graphql different from REST API?

Graphql is different from REST APIs in that it allows clients to request specific data from the server. This leads to more performant applications overall. Graphql also has a single endpoint while REST APIs have multiple endpoints. It is also different in that it only uses one Request method(POST) while REST APIs use five methods(POST, GET, PUT, PATCH and DELETE)

What are the benefits of using Graphql?


Graphql helps improve performance because clients request the data they need.
Graphql provides strongly typed schemas which ensures the client and server use the correct datatypes.
Graphql also helps reduce over-fetching and under-fetching. This is because the data manipulation and formatting are offloaded to the client thus allowing developers to retrieve the data they need.
Graphql APIs are also loosely coupled meaning that changes can be made at the server level and they would not introduce breaking changes to the clients.

Conclusion

Graphql offers improved performance, flexibility, and ease of use over traditional REST APIs. By leveraging the power of Laravel and Graphql, developers can build powerful APIs that are scalable, efficient, and easy to maintain.

In this article, we created a simple Graphql API in laravel and learnt key concepts in Graphql.

I hope this tutorial has been helpful and inspires you to explore the exciting possibilities of Laravel Graphql. If you want to learn how to build a REST API in Laravel then you can read this article.

Thank you for reading.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *