🚀 Nuova versione beta disponibile! Feedback o problemi? Contattaci

Schema, Tipo e Risolutore in GraphQL: Comprendere la Struttura di Base

Codegrind Team•Sep 03 2024

In GraphQL, lo schema, i tipi e i risolutori sono i tre elementi fondamentali che costituiscono la base di qualsiasi API. Lo schema definisce la struttura dell’API, i tipi descrivono i dati disponibili, e i risolutori gestiscono la logica per recuperare quei dati. In questo articolo esploreremo come questi componenti lavorano insieme, come definirli e implementare un’API GraphQL flessibile e potente.

Cos’è lo Schema in GraphQL?

Lo schema in GraphQL definisce la struttura dell’API, specificando quali tipi di dati possono essere richiesti e come le query, le mutations e le subscriptions possono interagire con quei dati. In altre parole, lo schema descrive tutte le operazioni disponibili che i client possono eseguire.

Lo schema è scritto utilizzando il GraphQL Schema Definition Language (SDL), che è una sintassi leggibile che permette di definire tipi di dati, campi, argomenti, tipi di input e molto altro.

Esempio di Schema di Base

type Query {
  user(id: ID!): User
  users: [User!]!
}

type User {
  id: ID!
  name: String!
  email: String!
}

type Mutation {
  createUser(name: String!, email: String!): User!
}

In questo schema:

  • Il tipo Query definisce due query: user (che accetta un ID e restituisce un singolo utente) e users (che restituisce un elenco di utenti).
  • Il tipo User rappresenta un utente con campi id, name e email.
  • Il tipo Mutation definisce una mutation createUser che consente di creare un nuovo utente con nome e email.

Cos’è un Tipo in GraphQL?

I tipi in GraphQL definiscono la struttura e la forma dei dati che possono essere richiesti e restituiti dall’API. Esistono diversi tipi fondamentali in GraphQL, come Int, Float, String, Boolean, e ID, e puoi anche definire tipi personalizzati (ad esempio, User, Post, Comment, ecc.).

Tipi di Base in GraphQL

  • Scalari: I tipi scalari sono i tipi di dati di base, come String, Int, Float, Boolean e ID.
  • Tipi Personalizzati: Definisci tipi di oggetti personalizzati come User, Post, ecc.
  • Tipi di Input: Utilizzati per inviare dati in una mutation (ad esempio, CreateUserInput).
  • Enum: Utilizzati per definire un insieme di valori possibili.

Esempio di Tipo Personalizzato

type Post {
  id: ID!
  title: String!
  content: String!
  author: User!
}

type User {
  id: ID!
  name: String!
  posts: [Post!]!
}

In questo esempio, un Post è collegato a un User, e un User può avere più Post. La relazione tra i tipi User e Post è gestita tramite il campo author nel tipo Post e il campo posts nel tipo User.

Cos’è un Risolutore in GraphQL?

Un risolutore è una funzione che contiene la logica per risolvere il valore di un campo in un tipo GraphQL. Quando una query viene eseguita, GraphQL chiama i risolutori associati ai campi definiti nello schema per ottenere o calcolare i dati richiesti.

Funzionamento dei Risolutori

Ogni campo di un tipo in GraphQL può avere un risolutore associato, che è responsabile di recuperare o generare il valore per quel campo. Se non viene fornito un risolutore esplicito per un campo, GraphQL utilizza un risolutore predefinito che restituisce il valore corrispondente nell’oggetto genitore.

Struttura di Base di un Risolutore

I risolutori accettano quattro argomenti principali:

  1. parent: L’oggetto genitore, che è il risultato del risolutore del livello superiore.
  2. args: Gli argomenti passati dal client.
  3. context: Un oggetto condiviso tra tutti i risolutori, utile per condividere dati come informazioni sull’utente o l’accesso al database.
  4. info: Informazioni sull’esecuzione della query.

Esempio di Risolutore

const resolvers = {
  Query: {
    user: (parent, { id }, context) => {
      return context.db.getUserById(id);
    },
    users: (parent, args, context) => {
      return context.db.getAllUsers();
    },
  },
  Mutation: {
    createUser: (parent, { name, email }, context) => {
      const newUser = { id: Date.now().toString(), name, email };
      context.db.createUser(newUser);
      return newUser;
    },
  },
  User: {
    posts: (user, args, context) => {
      return context.db.getPostsByUserId(user.id);
    },
  },
};

In questo esempio:

  • I risolutori per le query user e users accedono al database per restituire gli utenti richiesti.
  • Il risolutore della mutation createUser crea un nuovo utente e lo aggiunge al database.
  • Il risolutore posts nel tipo User recupera tutti i post associati a un utente specifico.

Implementazione Completa di Schema e Risolutori

Vediamo come combinare schema e risolutori in un’implementazione completa di GraphQL.

1. Definire lo Schema

type Query {
  user(id: ID!): User
  users: [User!]!
}

type User {
  id: ID!
  name: String!
  email: String!
  posts: [Post!]!
}

type Post {
  id: ID!
  title: String!
  content: String!
  author: User!
}

type Mutation {
  createUser(name: String!, email: String!): User!
}

2. Implementare i Risolutori

const resolvers = {
  Query: {
    user: async (parent, { id }, context) => {
      return await context.db.getUserById(id);
    },
    users: async (parent, args, context) => {
      return await context.db.getAllUsers();
    },
  },
  Mutation: {
    createUser: async (parent, { name, email }, context) => {
      const newUser = { id: Date.now().toString(), name, email };
      await context.db.createUser(newUser);
      return newUser;
    },
  },
  User: {
    posts: async (user, args, context) => {
      return await context.db.getPostsByUserId(user.id);
    },
  },
  Post: {
    author: async (post, args, context) => {
      return await context.db.getUserById(post.authorId);
    },
  },
};

3. Creare il Server Apollo

const { ApolloServer } = require("apollo-server");
const { typeDefs } = require("./schema");
const { resolvers } = require("./resolvers");
const db = require("./db"); // Database con funzioni getUserById, getAllUsers, createUser, etc.

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: () => ({ db }),
});

server.listen().then(({ url }) => {
  console.log(`Server ready at ${url}`);
});

4. Eseguire Query e Mutations

Esempio di query per recuperare tutti gli utenti con i loro post:

query {
  users {
    id
    name
    posts {
      title
      content
    }
  }
}

Esempio di mutation per creare un nuovo utente:

mutation {
  createUser(name: "Alice", email: "alice@example.com") {
    id
    name
    email
  }
}

Best Practices per Definire Schema e Risolutori

  1. Separazione delle ResponsabilitĂ : Mantieni separati lo schema e i risolutori per migliorare la leggibilitĂ  e la manutenibilitĂ .
  2. Risolutori Specifici: Associa risolutori specifici per campi complessi o relazioni, come campi che richiedono calcoli o accesso a piĂą fonti di dati.
  3. Gestione degli Errori: Implementa una solida gestione degli errori nei risolutori per restituire mess

aggi chiari al client. 4. Caching e Ottimizzazione: Utilizza tecniche come DataLoader per batchare le richieste e ottimizzare le prestazioni delle query.

Conclusione

Lo schema, i tipi e i risolutori sono i pilastri su cui si basano le API GraphQL. Lo schema definisce la struttura e le operazioni disponibili, i tipi descrivono i dati, e i risolutori gestiscono la logica di recupero dei dati. Implementando in modo corretto questi elementi e seguendo le best practices, puoi costruire API potenti, flessibili e facilmente scalabili.