GraphQL è la nuova parola d’ordine nello sviluppo di API. Sebbene le API RESTful rimangano la soluzione più popolare per esporre i dati delle applicazioni, presentano molti limiti che GraphQL si propone di risolvere.

GraphQL è un query language creato da Facebook e trasformato in un progetto open-source nel 2015. Offre una sintassi intuitiva e flessibile per descrivere e accedere ai dati di un’API.

In questa guida scopriremo come creare un progetto GraphQL Node.js. Utilizzeremo GraphQL per costruire un’applicazione Todo nel framework web Express.js per Node.

Cos’è GraphQL?

Dalla documentazione ufficiale: “GraphQL è un linguaggio di interrogazione per le API e un runtime per soddisfare tali interrogazioni con i dati esistenti. GraphQL fornisce una descrizione completa e comprensibile dei dati della vostra API, dà ai clienti la possibilità di chiedere esattamente ciò di cui hanno bisogno e nient’altro, rende più facile l’evoluzione delle API nel tempo e rende disponibili potenti strumenti per gli sviluppatori”

GraphQL è un runtime lato server per l’esecuzione di query che utilizzano il sistema di tipi definito per i dati. Inoltre, GraphQL non è legato ad alcun database o motore di archiviazione specifico. Al contrario, si appoggia al vostro codice e al vostro archivio dati esistente. Troverete un confronto dettagliato di queste tecnologie nella guida GraphQL vs. API RESTful.

Per creare un servizio GraphQL, iniziate definendo i tipi di schema e creando i campi che utilizzano tali tipi. Poi fornite un function resolver da eseguire su ogni campo e tipo ogni volta che i dati vengono richiesti dal client.

La Terminologia di GraphQL

Il sistema di tipi di GraphQL viene utilizzato per descrivere quali dati possono essere interrogati e quali dati possono essere manipolati. È il cuore di GraphQL. Vediamo quali sono i diversi modi in cui possiamo descrivere e manipolare i dati in GraphQL.

Tipi Oggetto

I tipi oggetto di GraphQL sono modelli di dati contenenti campi fortemente tipizzati. Dovrebbe esserci una mappatura 1 a 1 tra i modelli e i tipi di GraphQL. Di seguito è riportato un esempio di tipo GraphQL:

type User {
  id: ID! # The "!" means required
  firstname: String
  lastname: String
  email: String
  username: String
  todos: [Todo] # Todo is another GraphQL type
}

Query

GraphQL Query definisce tutte le query che un client può eseguire sull’API di GraphQL. Per convenzione, occorre definire un sito RootQuery che conterrà tutte le query esistenti.

Di seguito definiamo e mappiamo le query alle corrispondenti API RESTful:

type RootQuery {
  user(id: ID): User           # Corresponds to GET /api/users/:id
  users: [User]                # Corresponds to GET /api/users
  todo(id: ID!): Todo    # Corresponds to GET /api/todos/:id
  todos: [Todo]          # Corresponds to GET /api/todos
}

Mutazioni

Se le GraphQL Query sono richieste GET, le mutazioni sono richieste POST, PUT, PATCH e DELETE che manipolano le API di GraphQL.

Mettiamo tutte le mutazioni in un unico RootMutation:

type RootMutation {
  createUser(input: UserInput!): User             # Corresponds to POST /api/users
  updateUser(id: ID!, input: UserInput!): User    # Corresponds to PATCH /api/users
  removeUser(id: ID!): User                       # Corresponds to DELETE /api/users
  createTodo(input: TodoInput!): Todo
  updateTodo(id: ID!, input: TodoInput!): Todo
  removeTodo(id: ID!): Todo
}

Avrete notato l’uso dei tipi -input per le mutazioni, come UserInput e TodoInput. È una buona pratica definire sempre i tipi di input per la creazione e l’aggiornamento delle risorse.

Potete definire i tipi di input come quello riportato di seguito:

input UserInput {
  firstname: String!
  lastname: String
  email: String!
  username: String!
}

Resolver

I resolver indicano a GraphQL cosa fare quando viene richiesta una query o una mutazione. Si tratta di una funzione di base che si occupa di raggiungere il livello del database per eseguire le operazioni CRUD (create, read, update, delete), di raggiungere un endpoint API RESTful interno o di chiamare un microservizio per soddisfare la richiesta del client.

Potete creare un nuovo file resolvers.js e aggiungere il seguente codice:

import sequelize from '../models';
export default function resolvers () {
  const models = sequelize.models;
  return {
    // Resolvers for Queries
    RootQuery: {
      user (root, { id }, context) {
        return models.User.findById(id, context);
      },
      users (root, args, context) {
        return models.User.findAll({}, context);
      }
    },
    User: {
      todos (user) {
        return user.getTodos();
      }
    },
  }
  // Resolvers for Mutations
  RootMutation: {
    createUser (root, { input }, context) {
      return models.User.create(input, context);    
    },
    updateUser (root, { id, input }, context) {
      return models.User.update(input, { ...context, where: { id } });
    },
    removeUser (root, { id }, context) {
      return models.User.destroy(input, { ...context, where: { id } });
    },
    // ... Resolvers for Todos go here
  }
}

Schema

Lo schema GraphQL è ciò che GraphQL espone al mondo. Pertanto, i tipi, le query e le mutazioni saranno inclusi nello schema per essere esposti.

Di seguito spieghiamo come esporre i tipi, le query e le mutazioni:

schema {
  query: RootQuery
  mutation: RootMutation
}

Nello script precedente, abbiamo incluso i siti RootQuery e RootMutation creati in precedenza per esporli al mondo.

Come Funziona GraphQL con Node.js ed Express.js?

GraphQL offre un’implementazione per tutti i principali linguaggi di programmazione e Node.js non ne è esente. Sul sito ufficiale di GraphQL c’è una sezione dedicata al supporto di JavaScript e ci sono anche altre implementazioni di GraphQL per semplificare la scrittura e la programmazione.

GraphQL Apollo fornisce un’implementazione per Node.js ed Express.js e permette di iniziare a lavorare facilmente con GraphQL.

Nella prossima sezione scoprirete come creare e sviluppare la vostra prima applicazione GraphQL con Node.js e il framework backend Express.js utilizzando GraphQL Apollo.

Configurare GraphQL con Express.js

Creare un server per l’API di GraphQL con Express.js è semplice. In questa sezione vedremo come costruire un server GraphQL.

Inizializzare il Progetto con Express

Per prima cosa, bisogna installare e configurare un nuovo progetto Express.js. Create una cartella per il vostro progetto e installate Express.js con questo comando:

cd <project-name> && npm init -y
npm install express

Il comando precedente crea un nuovo file package.json e installa la libreria Express.js nel progetto.

Successivamente, struttureremo il nostro progetto come mostrato nell’immagine qui sotto. Conterrà diversi moduli per le funzionalità del progetto, come gli utenti, i todos, ecc.

Un elenco di file in graphql-todo.
I file del progetto graphql-todo.

Inizializzare GraphQL

Iniziamo installando le dipendenze Express.js di GraphQL con il seguente comando:

npm install apollo-server-express graphql @graphql-tools/schema --save

Creare Schemi e Tipi

Successivamente, creeremo un file index.js all’interno della cartella modules e aggiungeremo il seguente frammento di codice:

const { gql } = require('apollo-server-express');
const users = require('./users');
const todos = require('./todos');
const { GraphQLScalarType } = require('graphql');
const { makeExecutableSchema } = require('@graphql-tools/schema');
const typeDefs = gql`
  scalar Time
  type Query {
    getVersion: String!
  }
  type Mutation {
    version: String!
  }
`;
const timeScalar = new GraphQLScalarType({
  name: 'Time',
  description: 'Time custom scalar type',
 serialize: (value) => value,
});
const resolvers = {
  Time: timeScalar,
  Query: {
    getVersion: () => `v1`,
  },
};
const schema = makeExecutableSchema({
  typeDefs: [typeDefs, users.typeDefs, todos.typeDefs],
  resolvers: [resolvers, users.resolvers, todos.resolvers],
});
module.exports = schema;

Analisi del Codice

Scomponiamo il frammento di codice:

Passo 1

Per prima cosa, abbiamo importato le librerie necessarie e creato i tipi di query e di mutazione predefiniti. Per ora, la query e la mutazione impostano solo la versione dell’API GraphQL. Ma estenderemo la query e la mutazione in modo da includere altri schemi man mano che procediamo.

Interfaccia a riga di comando che mostra il codice "const" per importare GraphQL e altre estensioni.
Importare GraphQL e le estensioni.
Passo 2:

Abbiamo poi creato un nuovo tipo scalare per il tempo e il nostro primo resolver per la query e la mutazione create in precedenza. Inoltre, abbiamo generato uno schema utilizzando la funzione makeExecutableSchema.

Lo schema generato include tutti gli altri schemi che abbiamo importato e ne includerà altri quando li creeremo e importeremo.

Interfaccia a riga di comando che mostra il codice "const" per creare il nostro tipo scalare e il nostro primo resolver.
Creiamo un tipo scalare per il tempo e il nostro primo resolver.

Il frammento di codice precedente mostra che abbiamo importato diversi schemi nella funzione makeExecutableSchema. Questo approccio ci aiuta a strutturare l’applicazione per renderla più complessa. Successivamente, creeremo gli schemi Todo e User che abbiamo importato.

Creare lo Schema Todo

Lo schema Todo mostra semplici operazioni CRUD che gli utenti dell’applicazione possono eseguire. Di seguito è riportato lo schema che implementa le operazioni CRUD di Todo.

const { gql } = require('apollo-server-express');
const createTodo = require('./mutations/create-todo');
const updateTodo = require('./mutations/update-todo');
const removeTodo = require('./mutations/delete-todo');
const todo = require('./queries/todo');
const todos = require('./queries/todos');
const typeDefs = gql`
  type Todo {
    id: ID!
    title: String
    description: String
    user: User
  }
  input CreateTodoInput {
    title: String!
    description: String
    isCompleted: Boolean
  }
  input UpdateTodoInput {
    title: String
    description: String
    isCompleted: Boolean
  }  extend type Query {
    todo(id: ID): Todo!
    todos: [Todo!]
  }
  extend type Mutation {
    createTodo(input: CreateTodoInput!): Todo
    updateTodo(id: ID!, input: UpdateTodoInput!): Todo
    removeTodo(id: ID!): Todo
  }
`;
// Provide resolver functions for your schema fields
const resolvers = {
  // Resolvers for Queries
  Query: {
    todo,
    todos,
  },
  // Resolvers for Mutations
  Mutation: {
    createTodo,
    updateTodo,
    removeTodo,
  },
};
module.exports = { typeDefs, resolvers };

Codice

Analizziamo il frammento di codice e scomponiamolo:

Passo 1:

Per prima cosa, abbiamo creato uno schema per il nostro Todo utilizzando le parole chiave GraphQL type, input e extend. La parola chiave extend viene utilizzata per ereditare e aggiungere nuove query e mutazioni alla query principale e alla mutazione esistente che abbiamo creato in precedenza.

Un'interfaccia a riga di comando che mostra lo schema del nostro script Todo, compresi i nuovi input.
Creazione dello schema per il nostro Todo.
Passo 2:

Successivamente, abbiamo creato un resolver, che viene utilizzato per recuperare i dati corretti quando viene richiamata una particolare query o mutazione.

Un'interfaccia a riga di comando che mostra il codice per creare un risolutore per il nostro Todo.
Creare un resolver.

Una volta creata la funzione resolver, possiamo creare singoli metodi per la logica e la manipolazione del database, come mostrato nell’esempio create-todo.js.

Create un file create-user.js nella cartella ./mutations/ e aggiungete la logica per creare un nuovo Todo nel vostro database.

const models = require('../../../models');
module.exports = async (root, { input }, context) => {
  return models.todos.push({ ...input });
};

Il frammento di codice qui sopra è un modo semplificato per creare un nuovo Todo nel nostro database utilizzando l’ORM Sequelize. Potete approfondire in Sequelize e su come configurarlo con Node.js.

Potrete seguire la stessa procedura per creare diversi schemi a seconda della vostra applicazione oppure clonare il progetto completo da GitHub.

Successivamente, configureremo il server con Express.js ed eseguiremo l’applicazione Todo appena creata con GraphQL e Node.js.

Configurazione ed Esecuzione del Server

Infine, configureremo il nostro server utilizzando la libreria apollo-server-express che abbiamo installato in precedenza.

apollo-server-express è un semplice wrapper di Apollo Server per Express.js, consigliato perché è stato sviluppato per essere adatto allo sviluppo di Express.js.

Utilizzando gli esempi precedenti, configuriamo il server Express.js in modo che lavori con la libreria apollo-server-express appena installata.

Create un file server.js nella directory principale e incollate il seguente codice:


const express = require('express');
const { ApolloServer } = require('apollo-server-express');
const schema = require('./modules');
const app = express();
async function startServer() {
  const server = new ApolloServer({ schema });
  await server.start();
  server.applyMiddleware({ app });
}
startServer();
app.listen({ port: 3000 }, () =>
  console.log(`Server ready at http://localhost:3000`)
);

Con il codice sopra riportato, avete creato il vostro primo server CRUD GraphQL per Todos e Users. Potete avviare il vostro server di sviluppo e accedere al playground utilizzando http://localhost:3000/graphql. Se tutto è andato a buon fine, dovreste visualizzare la schermata seguente:

Un'interfaccia di sviluppo che mostra una semplice query di risposta.
La schermata di verifica.

Riepilogo

GraphQL è una moderna tecnologia supportata da Facebook che semplifica il noioso lavoro di creazione di API su larga scala con modelli architettonici RESTful.

Questa guida ha illustrato GraphQL e ha dimostrato come sviluppare la prima API GraphQL con Express.js.

Fateci sapere cosa costruite utilizzando GraphQL nei commenti qui sotto.

Solomon Eseme

Sono un software engineer e un content creator orientato a costruire prodotti innovativi e ad alte prestazioni seguendo le migliori pratiche e gli standard del settore. Amo anche scrivere su Masteringbackend.com. Seguitemi su X, LinkedIn e About me.