GraphQL en PHP

GraphQL en PHP

Integración Laravel y Lighthouse

Introducción

Laravel es un framework de desarrollo web de código abierto, escrito en PHP, que sigue el patrón de arquitectura MVC (Modelo-Vista-Controlador); ofrece una amplia gama de características y funcionalidades que agilizan el proceso de desarrollo, como enrutamiento flexible, gestión de sesiones, generación de consultas SQL, migraciones de base de datos, autenticación de usuarios, caché, entre otras.

Lighthouse es una biblioteca de código abierto para Laravel que permite construir servidores GraphQL de manera sencilla y eficiente. Simplifica la implementación de GraphQL en Laravel al proporcionar características como la generación automática de esquemas GraphQL a partir de los modelos de Eloquent (el ORM de Laravel), resolvers predeterminados para resolver consultas y mutaciones básicas, gestión de autorización y autenticación, paginación, validación de entradas GraphQL, entre otros.

En este tutorial, aprenderemos cómo crear una API integrando Laravel y Lighthouse.

Proyecto

Consiste en crear una API utilizando el framework Laravel y la biblioteca Lighthouse para implementar un servidor GraphQL. La API permitirá gestionar los datos de dos modelos: "Director" y "Movie". Estableceremos una relación entre ellos, donde un director puede tener varias películas y cada película pertenece a un director.

La API nos permitirá gestionar las operaciones CRUD (Create, Read, Update, Delete) para realizar acciones como crear, leer, actualizar y eliminar directores y películas.

Instalación y configuración

Comencemos creando un nuevo proyecto Laravel con el siguiente comando.

composer create-project --prefer-dist laravel/laravel laravel-lighthouse

Yo llamé al proyecto "laravel-lighthouse"; entra a la carpeta generada y continuamos con la instalación de la biblioteca de "Lighthouse".

composer require nuwave/lighthouse

Una vez finalizada la instalación, ejecuta el siguiente comando para publicar los archivos de configuración de Lighthouse.

php artisan vendor:publish --tag=lighthouse-schema

Esto creará un fichero graphql/schema.graphql , en donde definiremos nuestros tipos de datos.

Continuamos con la instalación de GraphiQL, una herramienta de desarrollo para probar nuestra API.

composer require mll-lab/laravel-graphiql

Finalizamos habilitando CORS para el endpoint de nuestra API en config/cors.php

<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Cross-Origin Resource Sharing (CORS) Configuration
    |--------------------------------------------------------------------------
    |
    | Here you may configure your settings for cross-origin resource sharing
    | or "CORS". This determines what cross-origin operations may execute
    | in web browsers. You are free to adjust these settings as needed.
    |
    | To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
    |
    */

    'paths' => ['api/*', 'graphql', 'sanctum/csrf-cookie'],

    'allowed_methods' => ['*'],

    'allowed_origins' => ['*'],

    'allowed_origins_patterns' => [],

    'allowed_headers' => ['*'],

    'exposed_headers' => [],

    'max_age' => 0,

    'supports_credentials' => false,

];

Continuamos con la configuración de nuestra base de datos.

Base de datos

Abre tu gestor de base de datos y crea uno para nuestro proyecto.

Yo lo llamé "laravel_lighthouse". Ahora abrimos el fichero .env para indicarle a Laravel que base de datos debe usar.

DB_DATABASE=laravel_lighthouse

Terminamos con la configuración, seguimos con la definición de las migraciones para nuestras tablas.

Migraciones

Comenzamos con la migración de la tabla directors . Ejecuta el siguiente comando.

php artisan make:migration create_directors_table --create=directors

Esto creará un fichero database/migrations/create_directors_table.php , lo abrimos y definimos los campos.

public function up(): void
    {
        Schema::create('directors', function (Blueprint $table) {
            $table->id();

            $table->string("name");
            $table->string("lastname");

            $table->timestamps();
        });
    }

Continuamos con la migración de la tabla movies . Ejecuta el comando.

php artisan make:migration create_movies_table --create=movies

Abre el fichero database/migrations/create_movies_tabla.php para definir los campos.

public function up(): void
    {
        Schema::create('movies', function (Blueprint $table) {
            $table->id();

            $table->string("title");
            $table->string("genre");

            $table->unsignedBigInteger("director_id");
            $table->foreign("director_id")->references("id")->on("directors")->onDelete("cascade");

            $table->timestamps();
        });
    }

Las líneas $table->unsignedBigInteger("director_id") y $table->foreign("director_id")->refereces("id")->on("directors")->onDelete("cascade") son importantes para crear la relación "uno a muchos" entre las dos tablas. La función onDelete("cascade") determina que si un registro en la tabla padre (directors) es borrado, los registros relacionados en la tabla hija (movies) también se borrarán. Es importante para que Laravel no dispare un error de integridad de base de datos.

Ejecutamos las migraciones con el siguiente comando.

php artisan migrate

Abre tu gestor de base de datos, deberías ver las tablas y la relación establecida.

A continuación los modelos Director y Movie.

Modelos

Para crear los modelos Director y Movie ejecuta los siguientes comandos.

php artisan make:model Director
php artisan make:model Movie

Se crearán los ficheros app/Models/Director.php y app/Models/Movie.php . Comenzamos con el modelo Director.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Director extends Model
{
    use HasFactory;

    protected $fillable = [
        "name",
        "lastname",
    ];

    public function movies() {
        return $this->hasMany(Movie::class);
    }
}

Primero indicamos los campos name y lastname como "mass assignable", luego la función public function movies() establece que este modelo puede tener muchas películas a través de $this->hasMany(Movie::class) .

Ahora continuamos con el modelo Movie.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Movie extends Model
{
    use HasFactory;

    protected $fillable = [
        "title",
        "genre",
        "director_id",
    ];

    public function director() {
        return $this->belongsTo(Director::class);
    }
}

Indicamos los campos "mass assignable" para title, genre y director_id. La función public function director() determina que este modelo pertenece a un director a través de $this->belongsTo(Director::class).

A continuación definimos los tipos de datos en el fichero de esquemas de graphql.

GraphQL

Para definir los tipos de datos, abre el fichero graphql/schema.graphql .

type Director {
  id: ID!
  name: String!
  lastname: String!
  created_at: DateTime!
  updated_at: DateTime!

  movies: [Movie!]! @hasMany
}

type Movie {
  id: ID!
  title: String!
  genre: String!
  created_at: DateTime!
  updated_at: DateTime!

  director: Director! @belongsTo
}

Lo que podemos destacar aquí son las directivas @hasMany y @belognsTo. Esto es una característica específica de Lighthouse y proporcionan una forma fácil y declarativa de agregar características avanzadas a tu API GraphQL.

Es este caso se define la relación "uno a muchos" entre los tipos de datos.

Ahora definimos las queries.

type Query {

  directors: [Director!] @all

  director(id: ID! @whereKey): Director @find

  movies: [Movie!] @all

  movie(id: ID! @whereKey): Movie @find

}

La integración de Lighthouse en Laravel permite generar automáticamente los resolvers correspondientes para manejar estas consultas.

  1. directors: [Director!] @all: Esta query devuelve una lista de todos los directores disponibles en la base de datos. La directiva @all indica que queremos obtener todos los registros de la tabla de directores.

  2. director(id: ID! @whereKey): Director @find: Esta query permite buscar un director específico por su id. La directiva @whereKey indica que el valor proporcionado para id se utilizará como clave de búsqueda en la base de datos.

  3. movies: [Movie!] @all: Esta query devuelve una lista de todas las películas disponibles en la base de datos. Al igual que la query directors, la directiva @all se utiliza para obtener todos los registros de la tabla de películas.

  4. movie(id: ID! @whereKey): Movie @find: Esta query permite buscar una película específica por su id. Al igual que la query director la directiva @whereKey indica que el valor proporcionado para id se utilizará como clave de búsqueda en la base de datos.

Ahora definimos las mutaciones.

type Mutation {

  createDirector(name: String!, lastname: String!): Director @create

  updateDirector(id: ID!, name: String!, lastname: String!): Director @update

  deleteDirector(id: ID! @whereKey): Director @delete

  createMovie(title: String!, genre: String!, director_id: ID!): Movie @create

  updateMovie(id: ID!, title: String!, genre: String!, director_id: ID!): Movie @update

  deleteMovie(id: ID! @whereKey): Movie @delete

}

Estas mutations utilizan las directivas @create, @update y @delete de Lighthouse para especificar las operaciones a realizar en la base de datos.

  1. createDirector(name: String!, lastname: String!): Director @create: Esta mutation permite crear un nuevo director. Toma dos argumentos: name y lastname, que son de tipo String! y se requieren para crear el director. La directiva @create indica que queremos realizar una operación de creación en la base de datos. La mutation devuelve un objeto de tipo Director.

  2. updateDirector(id: ID!, name: String!, lastname: String!): Director @update: Esta mutation permite actualizar un director existente. Toma tres argumentos: id, name y lastname. El argumento id es de tipo ID! y se requiere para identificar el director que se actualizará. Los argumentos name y lastname son de tipo String! y se utilizan para actualizar los campos correspondientes del director. La directiva @update indica que queremos realizar una operación de actualización en la base de datos. La mutation devuelve un objeto de tipo Director.

  3. deleteDirector(id: ID! @whereKey): Director @delete: Esta mutation permite eliminar un director existente. Toma un argumento id de tipo ID!, que se requiere para identificar el director que se eliminará. La directiva @whereKey indica que el valor proporcionado para id se utilizará como clave de búsqueda en la base de datos. La directiva @delete indica que queremos realizar una operación de eliminación en la base de datos. La mutation devuelve un objeto de tipo Director.

Con estas mutaciones tenemos todas las operaciones CRUD para el modelo Director.

Las mutaciones para el modelo Movie son:

  1. createMovie(title: String!, genre: String!, director_id: ID!): Movie @create: Esta mutation permite crear una nueva película. Toma tres argumentos: title, genre y director_id. Los argumentos title y genre son de tipo String! y se requieren para crear la película. El argumento director_id es de tipo ID! y se utiliza para establecer la relación entre la película y el director correspondiente. La directiva @create indica que queremos realizar una operación de creación en la base de datos. La mutation devuelve un objeto de tipo Movie.

  2. updateMovie(id: ID!, title: String!, genre: String!, director_id: ID!): Movie @update: Esta mutation permite actualizar una película existente. Toma cuatro argumentos: id, title, genre y director_id. El argumento id es de tipo ID! y se requiere para identificar la película que se actualizará. Los argumentos title, genre y director_id son de tipo String! y ID!, respectivamente, y se utilizan para actualizar los campos correspondientes de la película. La directiva @update indica que queremos realizar una operación de actualización en la base de datos. La mutation devuelve un objeto de tipo Movie.

  3. deleteMovie(id: ID! @whereKey): Movie @delete: Esta mutation permite eliminar una película existente. Toma un argumento id de tipo ID!, que se requiere para identificar la película que se eliminará. La directiva @whereKey indica que el valor proporcionado para id se utilizará como clave de búsqueda en la base de datos. La directiva @delete indica que queremos realizar una operación de eliminación en la base de datos. La mutation devuelve un objeto de tipo Movie.

Hemos finalizado con todas las operaciones CRUD de nuestra API. Pasaremos a probarlos con GraphiQL.

Probando la API

Iniciamos el servidor de desarrollo con el comando.

php artisan serve

Abrimos la herramienta de desarrollo de GraphiQL en localhost:8000/graphiql .

Haciendo clic en la esquina superior izquierda puedes consultar los tipos definidos para nuestra API.

Comenzamos provando las mutaciones para añadir algunos directores y películas.

Añadí tres directores: Denis Villeneuve, Alejandro González Iñarritu y Alfonso Cuarón.

Podemos verificarlo en la base de datos.

Ahora agreguemos las películas.

Ten el cuenta como el campo director_id vincula la película con su correspondiente director.

Añadí cuatro, de Villenevue, Dune y Blade Runner 2049, de Iñarritu Revenant, y de Cuarón Gravedad.

Lo vemos en la base de datos.

Ahora que tenemos datos en nuestras tablas, probemos algunas queries.

Consultamos todos los directores y además pedimos las películas que tienen. Podemos ver que la relación funciona correctamente.

Ahora consultamos las películas.

Consultamos el nombre de la película y el nombre y apellido del director. Todo funciona correctamente.

Probamos las consultas por ID.

Puedes seguir prabando las demás consultas y mutaciones. Por ahora finalizamos con el tutorial.

Conclusión

Hemos explorado cómo crear una API utilizando Laravel y Lighthouse para implementar un servidor GraphQL. Hemos creado dos modelos, "Director" y "Movie", y hemos establecido una relación entre ellos: un director puede tener varias películas y cada película pertenece a un director.

Hemos explorado cómo Laravel y Lighthouse trabajan en conjunto para simplificar y agilizar el desarrollo de APIs GraphQL. Hemos aprovechado las funcionalidades de Laravel, como las migraciones, los modelos de Eloquent y las operaciones de consulta, y hemos utilizado Lighthouse para generar automáticamente los resolvers y agregar características avanzadas a nuestra API.

Puedes encontrar el proyecto completo en mi repositorio de GitHub haciendo clic AQUÍ.

Espero que lo hayas encontrado entretenido, instructivo y claro. Si tienes alguna duda, puedes hacérmelo saber en los comentarios. Pronto estaré subiendo más tutoriales.

Nos vemos en la próxima. Saludos!👋😊