Guida introduttiva a GraphQL: come funziona e come usarlo

Guida introduttiva a GraphQL: come funziona e come usarlo

GraphQL è un linguaggio basato su query per la costruzione di API.
Realizzato da Facebook nel 2012, permette di mostrare i differenti tipi di dato restituibili dal server e mette al servizio del client uno strumento potente, semplice ed ultra veloce di estrapolazione dati, in un’unica chiamata, in un unico endpoint.

Puoi consultare https://graphql.org/ per scoprirne tutti i vantaggi.

Ciò che mette gli sviluppatori in allarme, è il fatto di non capire fino in fondo come funziona finché non vedono il linguaggio in azione. Quindi iniziamo subito!

In questo articolo useremo GraphQL insieme a NodeJS, anche se può essere integrato con moltissimi altri linguaggi: Python, Ruby, Java, C#, Go, PHP, etc.


Pre-requisito

Installa NodeJS da qui: https://nodejs.org/it/.

Come usare GraphQL con NodeJS

Crea una cartella chiamata graphql-nodejs. Apri il terminale (su Mac o Linux) o il Prompt dei comandi (su windows), spostati nella cartella che hai precedentemente creato e lancia il comando npm init per creare un nuovo progetto NodeJS in quella cartella. Ecco i comandi in basso.

cd graphql-nodejs
npm init

Installa le dipendenze

Installa Express utilizzando il comando:

npm install express

Installa GraphQL con il seguente comando. Andremo ad installare sia il componente base che il corrispettivo per express.

npm install express-graphql graphql

Codice NodeJS

Crea un file chiamato server.js dentro la cartella del progetto e copia il seguente codice all’interno.

const express = require('express');
const port = 5000;
const app = express();

app.get('/hello', (req,res) => {
    res.send("hello");
   }
);

app.listen(port);
console.log('Indirizzo endpoint: localhost:${port}');

Il codice sopra ha solo un singolo endpoint che “ascolta” in GET e si chiama /hello.

L’endpoint è stato creato usando Express.

Ora andiamo a modificare questo codice per sfruttare GraphQL.

Inseriamo GraphQL nel codice

GraphQL avrà solo un singolo indirizzo di endpoint chiamato /graphql che si occuperà di gestire tutte le requests.

Sostituisci il seguente codice dentro server.js:

// prendo tutte le librerie di cui ho bisogno
const express = require('express');
const graphqlHTTP = require('express-graphql');
const {GraphQLSchema} = require('graphql');

const {queryType} = require('./query.js');

// inizializza il numero della porta e di express
const port = 5000;
const app = express();

// Define the Schema
const schema = new GraphQLSchema({ query: queryType });

// Configura l'endpoint
app.use('/graphql', graphqlHTTP({
    schema: schema,
    graphiql: true,
}));

app.listen(port);
console.log('Indirizzo endpoint: localhost:${port}');

Ora analizziamo questo codice.

graphqlHTTP ci permette di configurare un server GraphQL all’indirizzo /graphql. Questo tool già sa come gestire le requests che arriveranno.

Questo settaggio è fatto nella seguente porzione del codice:

app.use('/graphql', graphqlHTTP({
    schema: schema,
    graphiql: true,
}));

Ora analizziamo i parametri dentro graphqlHTTP.


graphiql

graphiql è una Web UI con la quale puoi testare gli endpoints realizzati con GraphQL. Andremo a settare il valore a true così sarà più facile per noi procedere con lo sviluppo.

schema

Come ho già detto, GraphQL ha un solo endpoint che comunica con l’esterno: /graphql. Questo endpoint può avere a sua volta multipli endpoint che fanno cose diverse, questi possono essere definiti nello schema.

Lo schema può fare cose come:

  • Specificare i sotto-endpoints
  • Indicare tutti gli inputs e tutti gli outputs
  • Indicare quale azione dovrebbe essere compiuta affinché un endpoint venga raggiunto e tanto altro.

Lo schema è definito come di seguito:

const schema = new GraphQLSchema({ query: queryType });

Lo schema può contenere query e mutation. In questo articolo ci concentreremo solo sulle query.

query

Puoi vedere nello schema che la query è stata settata nella variabile queryType.

Noi importiamo queryType da file query.js usando il seguente comando:

const {queryType} = require('./query.js');

query.js è un file che andremo a creare a breve.

query è dove andremo a specificare i nostri endpoints read-only nello schema.

Ora è venuto il momento di creare il file query.js nel progetto. Copia lo snippet di codice in basso al suo interno.

const { GraphQLObjectType,
    GraphQLString
} = require('graphql');


// Definisci la Query
const queryType = new GraphQLObjectType({
    name: 'Query',
    fields: {
        hello: {
            type: GraphQLString,

            resolve: function () {
                return "Hello World";
            }
        }
    }
});

exports.queryType = queryType;

Spiegazione della query

Al queryType è associato l’istanza di un oggetto GraphQLObjectType e gli è stato dato il nome Query.

Nei fields invece vengono specificati i vari endpoints.

Quindi sarà proprio quì che andremo ad aggiungere il nostro hello.

hello ha un tipo di GraphQLString, il che significa che questo endpoint ritorna un tipo stringa. Definiamo un GraphQLString e non String perché ci troviamo in uno schema GraphQL, quindi usare direttamente String non funzionerebbe.

La funzione resolve indica l’azione che deve essere compiuta quando l’endpoint è richiamato.
In questo caso l’azione è ritornare una String “Hello World”.

Infine, esportiamo il querytype usando exports.queryType = queryType. Questo per assicurarci di poterlo importare in server.js.

Lanciamo l’Applicazione

Lanciamo l’applicazione usando il seguente comando:

node server.js

A questo punto aprendo il nostro browser all’indirizzo localhost:5000/graphql potremo iniziare a testare la nostra applicazione.

Questo URL mostrerà la Graphiql web UI come mostrato nello screen in basso.

L’input è inviato sulla sinistra e l’output è mostrato sulla destra.

Dando il seguente input:

{
  hello
}

Questo è quello che riceveremo in risposta:

{
  "data": {
    "hello": "Hello World"
  }
}

Congratulazioni 😀

Hai creato il tuo primo endpoint in GraphQL.


Aggiungere altri endpoints

Andremo a creare 2 nuovi endpoints:

  • film: Dato un ID, questo endpoint ritornerà un film.
  • regista: Questo endpoint restituirà un regista dato il suo ID e tutti i film da lui diretti.

Aggiungere dei dati

Solitamente un’applicazione legge i dati da un Database. Ma per questo tutorial andremo a creare un file chiamato data.js ed aggiungeremo all’interno il seguente codice.

// Dati fittizi
let movies = [{
    id: 1,
    name: "Movie 1",
    year: 2018,
    directorId: 1
},
{
    id: 2,
    name: "Movie 2",
    year: 2017,
    directorId: 1
},
{
    id: 3,
    name: "Movie 3",
    year: 2016,
    directorId: 3
}
];

let directors = [{
    id: 1,
    name: "Director 1",
    age: 20
},
{
    id: 2,
    name: "Director 2",
    age: 30
},
{
    id: 3,
    name: "Director 3",
    age: 40
}
];

exports.movies = movies;
exports.directors = directors;

Questo file contiene tutti i dati di cui abbiamo bisogno riguardanti film e registi, ed andremo ad utilizzarli nel nostro applicativo.

Aggiungiamo l’endpoint “movie” alla query

Il nuovo endpoint verrà aggiunto al queryType all’interno di query.js.

Ecco il codice:

movie: {
            type: movieType,
            args: {
                id: { type: GraphQLInt }
            },
            resolve: function (source, args) {
                return _.find(movies, { id: args.id });
            }
        }

I tipo restituito è movieType che andremo a definire a breve.

Il parametro args è usato per indicare l’input dell’endpoint. In questo caso è id di tipo GraphQLInt.

La funzione resolve ritorna il film corrispondente all’id, dalla lista che abbiamo fornito.

La funzione find proviene dalla libreria lodash e si occupa di cercare un elemento in una lista.

Ecco il codice completo per query.js:

const { GraphQLObjectType,
    GraphQLString,
    GraphQLInt
} = require('graphql');
const _ = require('lodash');

const {movieType} = require('./types.js');
let {movies} = require('./data.js');


// Definisci la query
const queryType = new GraphQLObjectType({
    name: 'Query',
    fields: {
        hello: {
            type: GraphQLString,

            resolve: function () {
                return "Hello World";
            }
        },

        movie: {
            type: movieType,
            args: {
                id: { type: GraphQLInt }
            },
            resolve: function (source, args) {
                return _.find(movies, { id: args.id });
            }
        }
    }
});

exports.queryType = queryType;

Dal codice in alto possiamo vedere come movieType è definito in types.js.

Creiamo il Tipo movieType

Crea un file chiamato types.js.

Aggiungi il seguente codice all’interno.

const {
    GraphQLObjectType,
    GraphQLID,
    GraphQLString,
    GraphQLInt
} = require('graphql');

// Define Movie Type
movieType = new GraphQLObjectType({
    name: 'Movie',
    fields: {
        id: { type: GraphQLID },
        name: { type: GraphQLString },
        year: { type: GraphQLInt },
        directorId: { type: GraphQLID }

    }
});

exports.movieType = movieType;

Come puoi vedere il movieType è di tipo GraphQLObjectType.

Ha 4 attributi: id, name, year e directorId. I tipi per ognuno di questi campi sono specificati nei dettagli.

Questi campi provengono direttamente da data.js. In poche parole abbiamo effettuato una mappatura del JSON fornito. In questo caso, riferito alla lista dei film.

Aggiungiamo la query ed il tipo per i registi

Come abbiamo fatto con i film, procediamo all’aggiunta dell’endpoint per i registi.

In query.js, l’endpoint director può essere aggiunto in questo modo:

director: {
            type: directorType,
            args: {
                id: { type: GraphQLInt }
            },
            resolve: function (source, args) {
                return _.find(directors, { id: args.id });
            }
        }

Aggiungiamo ora il directorType al file types.js:

// Definisco il directorType
directorType = new GraphQLObjectType({
    name: 'Director',
    fields: {
        id: { type: GraphQLID },
        name: { type: GraphQLString },
        age: { type: GraphQLInt },
        movies: {
            type: new GraphQLList(movieType),
            resolve(source, args) {
                return _.filter(movies, { directorId: source.id });
            }

        }

    }
});

Fermiamoci un attimo. Il directorType è leggermente differente dal movieType. Perché questo?

Perché c’è una funzione resolve dentro directorType? Precedentemente abbiamo visto che la funzione resolve era presente solo nella query


La speciale natura del directorType

Quando viene chiamato l’endpoint dei registi, dobbiamo restituire i dettagli del regista e tutti i film che il regista ha diretto.

I primi 3 campi id, name, age nel directorType sono abbastanza chiari e vengono direttamente dai dati (lista dei registi).

Il 4 attributo invece, movies, deve contenere la lista dei film diretti da quel regista.

Per questa ragione, stiamo dicendo al nostro QueryType che il tipo di movies è GraphQLList di movieType (lista dei film).

Ma come facciamo esattamente a trovare quali film sono stati diretti da quel regista?

Per fare ciò, abbiamo una funzione resolve dentro il campo movies. Gli inputs di questo resolve sono source e args.

source conterrà tutti i dettagli dell’oggetto parent.

Assumi di avere una struttura di questo tipo: id = 1, name = “Random” e age = 20 per i registi.

In questo esempio, la funzione resolve andrà a trovare tutti i film dove directorId è uguale all’Id del regista richiesto.

Testiamo l’applicazione

Ora testiamo l’applicazione in degli scenari differenti.

Lanciamola con il comando node server.js.

Vai a localhost:5000/graphql e prova i seguenti inputs.

movie

Input:

{
  movie(id: 1) {
    name
  }
}

Outputs:

{
  "data": {
    "movie": {
      "name": "Movie 1"
    }
  }
}

Da questo primo output, possiamo notare che il client può richiedere esattamente ciò che vuole, anche porzioni ridotte di dati, e GraphQL restituirà solo quei parametri. In questo caso solo il campo name è stato richiesto, pertanto solo questo ha ricevuto indietro da server.

In movie(id: 1), l’id è il parametro di input. Stiamo chiedendo al server di restituirci il film che ha un id = 1.

Input:

{
  movie(id: 3) {
    name
    id
    year
  }
}

Output:

{
  "data": {
    "movie": {
      "name": "Movie 3",
      "id": "3",
      "year": 2016
    }
  }
}

Nell’esempio sono richiesti i campi name, id e year. Quindi il server restituisce tutte queste informazioni.

director

Input:

{
  director(id: 1) {
    name
    id,
    age
  }
}

Output:

{
  "data": {
    "director": {
      "name": "Director 1",
      "id": "1",
      "age": 20
    }
  }
}

Input:

{
  director(id: 1) {
    name
    id,
    age,
    movies{
      name,
      year
    }
  }
}

Output:

{
  "data": {
    "director": {
      "name": "Director 1",
      "id": "1",
      "age": 20,
      "movies": [
        {
          "name": "Movie 1",
          "year": 2018
        },
        {
          "name": "Movie 2",
          "year": 2017
        }
      ]
    }
  }
}

Nell’esempio sopra, vediamo la potenza di GraphQL. Innanzi tutto indichiamo che vogliamo un regista con id 1. Inoltre, indichiamo che vogliamo tutti i film di questo regista. Entrambi i campi di regia e film sono personalizzabili e il client può richiedere esattamente ciò che vuole.

Allo stesso modo, questo può essere esteso ad altri campi e tipi. Ad esempio, potremmo eseguire una query come Trova un regista con id 1. Per questo regista trova tutti i film. Per ognuno dei film trovi gli attori. Per ogni attore ottieni i primi 5 film classificati e così via. Per questa query, è necessario specificare la relazione tra i tipi. Una volta che lo facciamo, il client può interrogare qualsiasi relazione voglia.


Complimenti 😀

Ora conosci le basi di GraphQL.

Puoi approfondire l’argomento leggendo la documentazione sul sito ufficiale.

Leave a Comment

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *