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.
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.director(id: ID! @whereKey): Director @find
: Esta query permite buscar un director específico por suid
. La directiva@whereKey
indica que el valor proporcionado paraid
se utilizará como clave de búsqueda en la base de datos.movies: [Movie!] @all
: Esta query devuelve una lista de todas las películas disponibles en la base de datos. Al igual que la querydirectors
, la directiva@all
se utiliza para obtener todos los registros de la tabla de películas.movie(id: ID! @whereKey): Movie @find
: Esta query permite buscar una película específica por suid
. Al igual que la querydirector
la directiva@whereKey
indica que el valor proporcionado paraid
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.
createDirector(name: String!, lastname: String!): Director @create
: Esta mutation permite crear un nuevo director. Toma dos argumentos:name
ylastname
, que son de tipoString!
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 tipoDirector
.updateDirector(id: ID!, name: String!, lastname: String!): Director @update
: Esta mutation permite actualizar un director existente. Toma tres argumentos:id
,name
ylastname
. El argumentoid
es de tipoID!
y se requiere para identificar el director que se actualizará. Los argumentosname
ylastname
son de tipoString!
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 tipoDirector
.deleteDirector(id: ID! @whereKey): Director @delete
: Esta mutation permite eliminar un director existente. Toma un argumentoid
de tipoID!
, que se requiere para identificar el director que se eliminará. La directiva@whereKey
indica que el valor proporcionado paraid
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 tipoDirector
.
Con estas mutaciones tenemos todas las operaciones CRUD para el modelo Director.
Las mutaciones para el modelo Movie son:
createMovie(title: String!, genre: String!, director_id: ID!): Movie @create
: Esta mutation permite crear una nueva película. Toma tres argumentos:title
,genre
ydirector_id
. Los argumentostitle
ygenre
son de tipoString!
y se requieren para crear la película. El argumentodirector_id
es de tipoID!
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 tipoMovie
.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
ydirector_id
. El argumentoid
es de tipoID!
y se requiere para identificar la película que se actualizará. Los argumentostitle
,genre
ydirector_id
son de tipoString!
yID!
, 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 tipoMovie
.deleteMovie(id: ID! @whereKey): Movie @delete
: Esta mutation permite eliminar una película existente. Toma un argumentoid
de tipoID!
, que se requiere para identificar la película que se eliminará. La directiva@whereKey
indica que el valor proporcionado paraid
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 tipoMovie
.
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!👋😊