Schema, Tipo e Risolutore in GraphQL: Comprendere la Struttura di Base
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) eusers
(che restituisce un elenco di utenti). - Il tipo
User
rappresenta un utente con campiid
,name
eemail
. - Il tipo
Mutation
definisce una mutationcreateUser
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
eID
. - 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:
- parent: L’oggetto genitore, che è il risultato del risolutore del livello superiore.
- args: Gli argomenti passati dal client.
- context: Un oggetto condiviso tra tutti i risolutori, utile per condividere dati come informazioni sull’utente o l’accesso al database.
- 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
eusers
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 tipoUser
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
- Separazione delle ResponsabilitĂ : Mantieni separati lo schema e i risolutori per migliorare la leggibilitĂ e la manutenibilitĂ .
- Risolutori Specifici: Associa risolutori specifici per campi complessi o relazioni, come campi che richiedono calcoli o accesso a piĂą fonti di dati.
- 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.