tag

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

Tiago Sousa

Software Engineer

Introductionlink

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 dependencieslink

In order to create a new laravel application with a GraphQL API we will install and setup a couple of things before:

Setting up Laravellink

Since we are using Laravel, we leverage the Laravel CLI in order to create a brand new application.

laravel new laragraph
copy

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 Saillink

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
copy

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
copy

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
copy

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'
copy

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
copy

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 Lighthouselink

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
copy

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
copy

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
copy

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
copy

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'], ];
copy

Setting up Sanctumlink

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
copy

Next, we will need to publish Sanctum configuration file:

sail artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
copy

and migrate our database

sail artisan migrate
copy

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;
...
}
copy

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', ];
copy

Our first "endpoint"link

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 Schemalink

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")
}
copy

Developing our first mutationlink

To create our mutation, let's take advantage of Lighthouse and run the following command in our terminal.

sail artisan lighthouse:mutation AuthMutation
copy

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,
];
}
}
copy

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
copy

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:

>>> User::factory()->create(['email' => '[email protected]']);
copy

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 {#3616
name: "Juvenal Jerde III",
email: "[email protected]",
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,
}
>>>
copy

Let's request!link

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 {
login(input: { email: "[email protected]", password: "password" }) {
token
}
}
copy

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"
}
}
}
copy

whoamilink

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
}
copy

Let's now make the request to the query that we just added to our schema:

{
me {
name
email
}
}
copy

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"
}
copy

Login

Give me some tests, please? 🥺link

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 Pestlink

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
copy
rm tests/Feature/ExampleTest.php
copy
  • Install Pest via Composer and Sail
sail composer require pestphp/pest --dev --with-all-dependencies
copy
  • Since we are using Laravel, let's install Pest plugin for the framework
sail composer require pestphp/pest-plugin-laravel --dev
copy
  • Install Pest for our project
sail artisan pest:install
copy
  • Run our tests
sail artisan test
copy

Configuring our testing environmentlink

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>
copy

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(); + } }
copy

Writting our login testslink

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
copy

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 loginlink

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([
'email' => '[email protected]',
'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',
],
],
]);
});
copy

Ensures that it returns an error when credentials are wronglink

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.');
});
copy

Retrieves the information about the logged userlink

it('retrieves logged in user information', function () {
$user = Sanctum::actingAs(User::factory()->create());
$this->graphQL(/** @lang GraphQL */'
{
me {
email
}
}
')->assertJson([
'data' => [
'me' => [
'email' => $user->email,
],
],
]);
});
copy

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:

Conclusionslink

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 🙌

Tiago Sousa

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!

Interested in collaborating with me?

Let's Talk
footer

All the rights reserved © Tiago Sousa 2022

memoji12

Designed with love by Mauricio Oliveira