Ultimamente, nel mio gruppo Facebook preferito, mi trovo spesso a parlare di quali argomenti affrontare su questo blog. L’ultima volta è toccato al Deploy di un’applicazione Laravel su Kubernetes, che pare abbia dato notevoli soddisfazioni.
E oggi? Beh, questa volta daremo uno sguardo, insieme, a come possiamo mettere su senza troppi problemi un’API GraphQL con Laravel. Con l’aiuto di un package davvero speciale: Lighthouse!
GraphQL? Cosa?
Tecnicamente, GraphQL è definibile come un linguaggio da usare per richiedere e manipolare dati via API. Lo ha tirato fuori dal cilindro Facebook, ormai nove anni fa, prima di essere rilasciato pubblicamente nel 2015. Se si vuole studiare nel dettaglio il linguaggio in questione, questa è la fonte ufficiale a cui attingere.
Non mi dilungherò in ulteriori spiegazioni albertoangiolesche, ma gli elementi che bisogna conoscere per iniziare lavorare con delle API GraphQL sono tre:
- Schema: come il termine suggerisce, è uno schema che descrive nel dettaglio tutte le possibili richieste che un client può fare all’API che costruiremo. Ad ogni chiamata, l’API verificherà tramite questo schema che quella in arrivo sia, effettivamente, una chiamata valida;
- Query: l’operazione più semplice da fare, in un’API GraphQL, è chiedere dei dati. Una query è esattamente questo: un’operazione di lettura dei dati dal servizio;
- Mutation: nel momento in cui abbiamo bisogno di modificare qualche dato, allora tocca usare una mutation. Il termine, infatti, descrive ogni operazione di modifica dei dati dell’API;
Rispetto ad una “classica” REST API, ovviamente, ci sono un po’ di differenze. Per citarne alcune:
- un solo endpoint da interrogare, a differenza di diversi endpoint per REST;
- per poter usare un’API GraphQL bisogna un po’ impratichirsi nel costruire delle query;
- in GraphQL è possibile richiedere solo i dati di cui abbiamo effettivamente bisogno, un vantaggio non indifferente;
Due delle API con cui ho lavorato di più in tutta la mia vita sono GraphQL: quelle di Facebook e Shopify.
E con Laravel?
Creare una GraphQL API con il nostro framework PHP preferito è, devo ammetterlo, sorprendentemente semplice. Esiste un package, infatti, chiamato Lighthouse, che semplifica di molto la vita dello sviluppatore in questo senso.
Vediamo quindi come installarlo e come prepararlo.
Installazione
Supponendo di avere un progetto “fresco” di Laravel, la prima cosa da fare è installare il package in questione, insieme ad un comodo tool per fare qualche test.
1 2 3 4 5 6 7 8 |
# installiamo il package... composer require nuwave/lighthouse # installiamo Laravel GraphQL Playground composer require mll-lab/laravel-graphql-playground # pubblichiamo il file schema di default! php artisan vendor:publish --tag=lighthouse-schema |
- Laravel GraphQL Playground è un’utilissima web app, accessibile da browser, che possiamo usare per fare le nostre prime richieste all’API che stiamo costruendo;
- Pubblicare il file schema di default, invece, ci permetterà di avere a disposizione un primo schema da usare come riferimento per le nostre query. Il package lo userà automaticamente per fare quasi tutto il lavoro!
La prima chiamata API
Visto che so bene quanto vi piace mettere le mani nella ciccia già da subito, vi accontento. Proviamo subito questo package, no?
Andiamo a curiosare nello schema che è stato pubblicato in graphql/schema.graphql.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
"A date string with format `Y-m-d`, e.g. `2011-05-23`." scalar Date @scalar(class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\Date") "A datetime string with format `Y-m-d H:i:s`, e.g. `2018-05-23 13:43:32`." scalar DateTime @scalar(class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\DateTime") type Query { users: [User!]! @paginate(defaultCount: 10) user(id: ID @eq): User @find } type User { id: ID! name: String! email: String! created_at: DateTime! updated_at: DateTime! } |
Possiamo notare principalmente tre cose:
- Troviamo un tipo User già definito. Corrisponde all’utente di una qualsiasi applicazione Laravel appena creata;
- All’inizio del file troviamo due definizioni di scalari: Date e DateTime. Sono delle definizioni che Lighthouse usa per trasformare automaticamente le date dei timestamp;
- A metà del file, invece, troviamo le prime due query! Precisamente, il package ci mette a disposizione una query per recuperare tutti gli utenti dell’applicazione, ed una invece per recuperare i dati di un utente specifico;
Possiamo trovare più informazioni su come leggere questo file qui, sul sito di GraphQL, nella sezione dedicata al linguaggio usato per lo schema. Detto questo… facciamo subito una prima chiamata!
Per prima cosa, accediamo a Laravel Tinker per aggiungere al volo un po’ di dati di prova.
1 2 3 4 5 |
# accediamo a Tinker! php artisan tinker # una volta entrati, eseguiamo >>> \App\Models\User::factory(10)->create() |
Fatto questo, accediamo al Playground andando a visitare http://localhost/graphql-playground. Inseriamo questa query nel pannello a sinistra:
1 2 3 4 5 6 7 |
{ user(id: 1) { id name email } } |
e clicchiamo sul tasto “Play” per eseguire la query.
Già qui possiamo testare una delle cose più interessanti delle API GraphQL: proviamo a togliere “email” dai campi richiesti. La query verrà eseguita lo stesso, ritornando effettivamente gli stessi dati senza, però, la mail dell’utente. Niente male, per non aver scritto nemmeno una riga di codice, no?
Ok, adesso aggiungiamo qualcosa di “nostro”.
Un po’ di cinema!
Ho immaginato una piccola API per gestire film. Quelli che torneremo a vedere al cinema molto presto (spero). Senza strafare, partiamo da qualcosa di molto basilare.
1 2 |
# Generiamo il necessario con... php artisan make:model -m Movie --factory |
Questo comando genererà il model, la migration ed una factory. Nella migration, aggiungiamo due colonne:
- title, il titolo del film (string);
- year, l’anno di uscita (uno small int sarà più che sufficiente);
Nella factory, modifichiamo il metodo definition così:
1 2 3 4 5 6 7 |
public function definition() { return [ 'title' => $this->faker->sentence, 'year' => $this->faker->numberBetween(1980, 2020) ]; } |
Non aggiungeremo altro per ora. Non scordiamoci di eseguire php artisan migrate e di generare qualche record di prova per la tabella dei film:
1 2 3 4 5 |
# Apriamo Tinker... php artisan tinker ... ed usiamo la factory! >>> \App\Models\Movie::factory(10)->create() |
Bene, è tutto.
Torniamo al nostro graphql/schema.graphql. Seguendo la traccia di quello che abbiamo visto prima, dobbiamo aggiungere una definizione per la nostra nuova query. Proprio come per gli utenti, infatti, definiamo…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
"A date string with format `Y-m-d`, e.g. `2011-05-23`." scalar Date @scalar(class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\Date") "A datetime string with format `Y-m-d H:i:s`, e.g. `2018-05-23 13:43:32`." scalar DateTime @scalar(class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\DateTime") type Query { users: [User!]! @paginate(defaultCount: 10, type: CONNECTION) user(id: ID @eq): User @find movies: [Movie!]! @paginate(defaultCount: 10, type: CONNECTION) movie(id: ID @eq): Movie @find } type User { id: ID! name: String! email: String! created_at: DateTime! updated_at: DateTime! } type Movie { id: ID! title: String! year: Int! created_at: DateTime! updated_at: DateTime! } |
Cosa abbiamo aggiunto?
- In fondo al file, la definizione del type Movie, che GraphQL riconoscerà adesso come Movie valido. Gli attributi sono quelli che abbiamo creato con la migration;
- Tra le query, la prima, quella che ci permette di ottenere (paginati) tutti i film inseriti nel database.
Nota: in generale preferisco usare la paginazione “CONNECTION”, che fa uso di un cursore. Potete trovare più dettagli a riguardo qui. - La seconda query invece è quella che ci permette di ottenere i dati di un singolo film partendo dal suo ID;
Fatto! Possiamo provare la nostra API. E no, non serve scrivere altro codice… bello, vero?
Riapriamo il playground e proviamo la query paginata:
1 2 3 4 5 6 7 8 9 10 11 |
query { movies(first: 10) { edges { node { id title year } } } } |
O magari quella di ricerca tramite ID:
1 2 3 4 5 6 7 |
query { movie(id: 1) { id title year } } |
Insomma, neanche una riga di codice da scrivere, solo la specifica da compilare. Certo, questo è un caso molto basilare… cosa succederà in situazioni più complesse?
Nella prossima parte di questo articolo, vedremo insieme:
- come definire ed usare le mutation, per modificare i dati della nostra API;
- andare oltre le basi del CRUD e creare una logica “custom” più complessa;
- come rappresentare le relazioni tra model, ed usare la stessa query in casi d’uso totalmente differenti tra loro;
- altre funzionalità offerte da questo package, come l’uso del throttling per evitare abusi da parte di altri sviluppatori, la validazione dei dati in input, l’ordinamento dei dati e l’uso di middleware per mettere in sicurezza la nostra API!
La seconda parte arriverà tra qualche giorno (update, la seconda parte è qui)… quindi rimanete nei paraggi! Se volete, posso avvisarvi io tramite la newsletter! Potete iscrivervi su questa pagina oppure usare il form a lato.
A presto!