Wednesday, 30 March 2022
a year ago
8 minutes read
- Laravel
- GraphQL
- Testing
- Backend
- PHP
Laragraph - Laravel and GraphQL - Part 1
This article is the first article of a series that I'm writing about two main tools that I absolutely love! Laravel and GraphQL. During this series we will create a typical blog application while exploring all the features available from Lighthouse, the tool that will add GraphQL support to our Laravel application. Of course, testing will also be covered 🚀
Tiago Sousa
Software Engineer
Introduction
When you are a Software Engineer you can't stop learning. You have to be always updated and learn new things whenever you can, otherwise you will get stalled. I already had the opportunity to work with GraphQL while I developed this website that you are currently seing but I wanted to dig more into it, understand how could I create an application with a GraphQL API instead of a typical REST api. GraphQL has a great potential regarding the way that the client-facing applications request data, avoiding the management of huge amounts of payloads that are not needed and retrieving only what really matters. For this blog post I will show you how to create a brand new laravel application and integrate it with GraphQL while developing a blog post application. And, to put the cherry on top of the cake, we will go step by step and create the login functionality with Laravel Sanctum.
Install and Configure your dependencies
In order to create a new laravel application with a GraphQL API we will install and setup a couple of things before:
Setting up Laravel
Since we are using Laravel, we leverage the Laravel CLI in order to create a brand new application.
laravel new laragraph
If you don't have the laravel CLI installed globally, take a look into all the possible ways that you can create a new laravel application from the documentation
Setting up Sail
Sail is one of the products developed by the laravel team and is one of the most important tools from its ecosystem (in my opinion) since it allows to containerize, with Docker, your application to ensure the same development environment for your team. With a couple of commands you can have your environment ready without installing tools on your machine.
You can install Sail as a dev
dependency for your laravel application by running:
composer require laravel/sail --dev
Since we are using docker to run the development environment, we need a docker file, right?
In order to have access to this docker file, and after installing the sail dependency from the previous command, you can run the sail:install
command.
php artisan sail:install
When running this command, the only thing that will be asked is to choose the kind of database that you want for your application.
Finally, you can run Sail in detached mode to boot your application
./vendor/bin/sail up -d
At this point, you should, and must, have access to your laravel application at http://localhost
.
Sail will be installed as a dev
dependency, so you will always have access to the CLI from the project directory but it's kind of annoying always having to write
./vendor/bin/sail
in your terminal each time that you want to use the Sail CLI to interact with your application.
or that, you can easily create an alias within your bash profile like below:
alias sail='[ -f sail ] && bash sail || bash vendor/bin/sail'
With this alias, you can run the Sail CLI and interact with your artisan commands, just by typing sail
.
As an example, you run your application with:
sail up -d
From now on, we will always use the sail
command each time that we want to interact with any kind of CLI in our application.
But this article isn't about Sail, so if you want to learn a little but more about it, feel free to have a look in the delightful documentation that the laravel team created.
Setting up Lighthouse
If you read my previous article, you read somewhere the word lighthouse
. Even if they have the same name, they aren't the same tool.
The Lighthouse that we will talk about today is a package for Laravel that will help you creating and maintaining Laravel application with GraphQL API's.
Let's start by installing Lighthouse with composer:
sail composer require nuwave/lighthouse
and publish the default GraphQL schema that will be available at graphql/schema.graphql
in your project directory:
sail artisan vendor:publish --tag=lighthouse-schema
By default, this package doesn't bring GraphQL Playground but you can easily add it to your dev environment with:
sail composer require mll-lab/laravel-graphql-playground
After this, you should have access to the playground at http://localhost/graphql-playground
.
Lighthouse comes with a lot of configurations by default but you can publish this file and edit the configuration for what suits you the best:
sail artisan vendor:publish --tag=lighthouse-config
This configuration file will be published at config/lighthouse.php
in your application directory.
To finish Lighthouse configuration, we just need to enable the CORS (Cross-Origin Resource Sharing) for your GraphQL endpoints at config/cors.php
by changing the path
value:
return [- 'paths' => ['api/*', 'sanctum/csrf-cookie'], + 'paths' => ['api/*', 'graphql', 'sanctum/csrf-cookie'], ];
Setting up Sanctum
And again, Laravel ecosystem shining 💫
Sanctum provides a full authentication system for SPAs, mobile applications and simple token based system. Sanctum allows every user to create multiple API tokens for their account while granting abilities/scopes to each token. Do you know that feature from GitHub when you create a PAT (Personal Access Token) and you grant privileges to that token? Sanctum allows you to do that in a really easily way while also providing multiple authentication systems.
For this series of articles, we will use Sanctum for our authentication system to authenticate users with our GraphQL API.
As we did previously, we will use sail
CLI to install Sanctum by executing the following command in your terminal
sail composer require laravel/sanctum
Next, we will need to publish Sanctum configuration file:
sail artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
and migrate our database
sail artisan migrate
The last step regarding Sanctum is to ensure that your User
model uses the HasApiTokens
trait;
namespace App\Models;use Illuminate\Database\Eloquent\Factories\HasFactory;use Illuminate\Foundation\Auth\User as Authenticatable;use Illuminate\Notifications\Notifiable;use Laravel\Sanctum\HasApiTokens;class User extends Authenticatable{use HasApiTokens, HasFactory, Notifiable;...}
The last configuration that is missing regarding Sanctum is to update the guard
from api
to sanctum
in lighthouse configuration.
To do that, go to you config/lighthouse.php
and update your guard like below:
return [- 'guard' => 'api', + 'guard' => 'sanctum', ];
Our first "endpoint"
Now that we have everything that we need installed and configured to run our application, let's get our hands dirty, shall we?
Updating our GraphQL Schema
The first thing that we will do is to add API authentication in order to allow users to login into their accounts. Typically, when you are developing a REST API, you start by defining your API endpoint. In GraphQL you only have one endpoint that will be used to run Queries and Mutations. Every "endpoint" should be present in our GraphQL schema, so let's start by creating our login mutation.
type AccessToken {token: String!}input LoginInput {email: String! @rules(apply: ["email"])password: String!}extend type Mutation {login(input: LoginInput @spread): AccessToken! @field(resolver: "App\\GraphQL\\Mutations\\AuthMutation@login")}
Developing our first mutation
To create our mutation, let's take advantage of Lighthouse and run the following command in our terminal.
sail artisan lighthouse:mutation AuthMutation
After running this command, you should have a new AuthMutation.php
file under app\GraphQL\Mutations
.
This mutation will be the responsible adding the logic to login a user with email and password.
namespace App\GraphQL\Mutations;use Illuminate\Auth\AuthManager;use Illuminate\Contracts\Auth\MustVerifyEmail;use Nuwave\Lighthouse\Exceptions\AuthenticationException;class AuthMutation{public function __construct(private AuthManager $authManager){}public function login($_, array $args){$userProvider = $this->authManager->createUserProvider('users');$user = $userProvider->retrieveByCredentials(['email' => $args['email'],'password' => $args['password'],]);if (!$user || !$userProvider->validateCredentials($user, $args)) {throw new AuthenticationException('The provided credentials are incorrect.');}if ($user instanceof MustVerifyEmail && !$user->hasVerifiedEmail()) {throw new AuthenticationException('Your email address is not verified.');}return ['token' => $user->createToken('login')->plainTextToken,];}}
Of course, if we are trying to login a user we need to have a user persisted in our database. Since we don't have any endpoint that allows us to create a user, let's use Laravel Tinker to interact with our application directly from our terminal.
sail artisan tinker
With Tinker you are able to run PHP code directly from your terminal which allows you to execute class methods.
With that said, we can easily create a new user with the UserFactory
that comes with Laravel default installation:
The output of this command should be a new instance of the User::class
and since we called the create()
method the data should be persisted in our database.
[!] Aliasing 'User' to 'App\Models\User' for this Tinker session.=> App\Models\User {#3616name: "Juvenal Jerde III",email_verified_at: "2022-03-08 21:03:55",#password: "$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi",#remember_token: "3e332qpLIt",updated_at: "2022-03-08 21:03:55",created_at: "2022-03-08 21:03:55",id: 1,}>>>
Let's request!
At this point, we already have:
- a user persisted in our database;
- login mutation defined in our GraphQL schema;
- the login logic defined in our
AuthMutation
.
With all of this done, let's finally try to make the request by accessing our playground (http://localhost/graphql-playground
) and running the mutation!
mutation {token}}
The output should be something similar to this, a brand new token that identifies the [email protected]
user and allow to make authenticated requests:
{"data": {"login": {"token": "1|idCdwe3Jt4mwwRryk9REU3mcd3yy6llBblIeAt5v"}}}
whoami
To ensure that this token really identifies a user and allows him to make authenticated requests let's update our schema again 😎 Lighthouse provides a simple way to get the logged user information by adding the following query:
type Query {me: User @auth}
Let's now make the request to the query that we just added to our schema:
{me {name}}
Sending the query to our graphql playground may have two outcomes:
- the data of the User if the request is authenticated;
- null if the request is not authenticated.
You can test both cases but, in order to get the data of the user that is identified by the token, we need to add the Bearer
token to the Authorization
Header:
{"Authorization": "Bearer idCdwe3Jt4mwwRryk9REU3mcd3yy6llBblIeAt5v"}
Give me some tests, please? 🥺
When we talk about tests in the PHP world, the first thing that crosses our minds is PHPUnit but, recently, someone joined the party! 🎉 🥳 This new player is called Pest and it's a framework built on top of PHPUnit with a syntax very similar to Jest, the javascript testing framework.
PHP
+
JEST
=
PEST
Installing Pest
For this series, we will use Pest to write our tests and the first one that we will write is for our login functionality. But first, we need to setup Pest for our application!
- Delete old test PHPUnit tests
rm tests/Unit/ExampleTest.php
rm tests/Feature/ExampleTest.php
- Install Pest via Composer and Sail
sail composer require pestphp/pest --dev --with-all-dependencies
- Since we are using Laravel, let's install Pest plugin for the framework
sail composer require pestphp/pest-plugin-laravel --dev
- Install Pest for our project
sail artisan pest:install
- Run our tests
sail artisan test
Configuring our testing environment
Now that we have Pest installed, let's configure our test environment. One of the most important things when we develop and run tests is to ensure that the environment is clean in order to not create dependencies between tests.
Laravel facilitates a lot this configuration! To configure and enable a clean environment for every test you just need to update two files.
The first one is the phpunit.xml
that is in the root directory of your application and the other one is the TestCase.php
that is under your test directory.
When you run your tests, you don't want them to mess up with the same database that you use for development environment and since we don't want to preserve the data between
tests why not just use an in-memory
sqlite
database?
For that, go to your phpunit.xml
and add the following configuration:
<php><env name="APP_ENV" value="testing"/><env name="BCRYPT_ROUNDS" value="4"/><env name="CACHE_DRIVER" value="array"/><env name="DB_CONNECTION" value="sqlite"/><env name="DB_DATABASE" value=":memory:"/><env name="MAIL_MAILER" value="array"/><env name="QUEUE_CONNECTION" value="sync"/><env name="SESSION_DRIVER" value="array"/><env name="TELESCOPE_ENABLED" value="false"/></php>
And that's it! You don't need to configure the database connection because, by default, Laravel already brings a sqlite database connection configured.
Now that we already have our database configured when we run our tests, let's ensure that every time that a test runs, it runs in a clean environment without any data persisted.
In order to achieve this goal, go to your tests/TestCase.php
file and add the RefreshDatabase
from Illuminate\Foundation\Testing\RefreshDatabase
.
If you want to know more about this Trait
feel free to have a look into Laravel Docummentation.
After the database configuration for our test environment, we still have to make a couple of configurations for Lighthouse to have some GraphQL helper functions when we write our tests.
As it is written in Lighthouse Docummentation, we have to use the MakesGraphQLRequests
and RefreshesSchemaCache
traits.
Also, we have to call bootRefreshesSchemaCache()
method in our class setup.
We can do this in our TestCase.php
file since it will be called every time that tests are running.
After this configuration, your TestCase.php
should be something very similar to:
namespace Tests;use Illuminate\Foundation\Testing\TestCase as BaseTestCase; + use Nuwave\Lighthouse\Testing\MakesGraphQLRequests; + use Nuwave\Lighthouse\Testing\RefreshesSchemaCache; + use Illuminate\Foundation\Testing\RefreshDatabase;abstract class TestCase extends BaseTestCase { use CreatesApplication; + use MakesGraphQLRequests; + use RefreshesSchemaCache; + use RefreshDatabase;+ protected function setUp(): void + { + parent::setUp(); + $this->bootRefreshesSchemaCache(); + } }
Writting our login tests
When we installed Pest, a couple of commands were added to our artisan command. One of them allows us to create a test file to write our tests in Pest. You can run the following artisan command to create a test file:
sail artisan pest:test LoginTest
In order to not make the this article too extensive, I will show you how you create a test for that:
- ensures a successfully user login;
- ensures that it returns an error when credentials are wrong;
- retrieves the information about the logged user.
Of course, more tests will be written for this application but you can dig into them directly from GitHub
Ensures a successfully user login
use App\Models\User;use Illuminate\Support\Facades\Hash;use Laravel\Sanctum\Sanctum;it('allows a user to login successfully', function () {$user = User::factory()->create(['password' => Hash::make('password')]);$this->graphQL(/** @lang GraphQL */'mutation ($email: String!, $password: String!) {login(input: { email: $email, password: $password }) {token}}',['email' => $user->email,'password' => 'password'])->assertJsonStructure(['data' => ['login' => ['token',],],]);});
Ensures that it returns an error when credentials are wrong
it('throws an error when credentials are invalid', function () {$this->graphQL(/** @lang GraphQL */'mutation {login(input: {email: "[email protected]",password: "supersecret"}) {token}}')->assertGraphQLErrorMessage('The provided credentials are incorrect.');});
Retrieves the information about the logged user
it('retrieves logged in user information', function () {$user = Sanctum::actingAs(User::factory()->create());$this->graphQL(/** @lang GraphQL */'{me {}}')->assertJson(['data' => ['me' => ['email' => $user->email,],],]);});
These aren't the only tests that were developed but to don't get too extensive (sorry it's already too extensive actually 😅) you can have a look at the GitHub repository
Since this series will be a very simple intro to Laravel and GraphQL, we will not develop the entire registration flow. Based on your stack for authentication (Sanctum or Passport) there are a couple of open-source packages that offer authentication out of the box:
Conclusions
This was the first part of a series that I will be writing while I explore these tools. I hope that this article can help someone in the future that is trying to develop a GraphQL API with Laravel!
For each article I will attach a PR so you can view all the changes! For this one, feel free to check it here.
If you found this article interesting, feel free to share it with your colleagues or friends, because you know... Sharing is caring!
Also, if you enjoy working at a large scale in projects with global impact and if you enjoy a challenge, please reach out to us at xgeeks! We're always looking for talented people to join our team 🙌
Written by Tiago Sousa
Hey there, my name is Tiago Sousa and I'm a Fullstack Engineer currently working at xgeeks. I'm a technology enthusiast and I try to explore new things to keep myself always updated. My motto is definitely "Sharing is caring" and that's exactly why you are currently reading this!