Scrittura di Query in GraphQL: Guida Completa
In GraphQL, le query sono il cuore del sistema di recupero dati, consentendo ai client di richiedere esattamente i dati di cui hanno bisogno in modo flessibile. A differenza delle API REST, dove è spesso necessario fare piĂš richieste per ottenere tutti i dati, in GraphQL puoi richiedere e ricevere solo i dati specifici in unâunica richiesta. Questo articolo esplorerĂ come scrivere query in GraphQL, con esempi pratici su come implementare query di base, avanzate e ottimizzate.
Cosâè una Query in GraphQL?
Le query sono operazioni di lettura in GraphQL che permettono ai client di ottenere dati dal server. Ogni query specifica i campi e le relazioni che il client desidera ricevere. Le query possono essere semplici, come richiedere un singolo oggetto, oppure complesse, come richiedere una lista di oggetti con dati correlati.
Esempio di Query di Base
query {
user(id: "1") {
id
name
email
}
}
In questo esempio:
- La query richiede i dettagli di un utente specifico (
user
) passando il parametroid
. - Restituisce i campi
id
,name
, eemail
relativi allâutente richiesto.
Scrivere una Query in GraphQL
Una query in GraphQL consiste in un insieme di campi specificati dal client, che il server risolverĂ . Ogni campo nel risultato della query deve essere esplicitamente richiesto.
1. Query Semplice
Un esempio di query per ottenere i dettagli di un singolo utente:
query {
user(id: "1") {
id
name
email
}
}
Questa query richiede i campi id
, name
e email
per lâutente con id
= 1. Il server risponderĂ con i dettagli dellâutente, come definito nello schema.
2. Query su PiĂš Oggetti
Ă possibile eseguire query che restituiscono elenchi di oggetti, come tutti gli utenti o tutti i post.
query {
users {
id
name
email
}
}
Questa query restituisce una lista di utenti, ognuno dei quali ha i campi id
, name
e email
.
3. Relazioni tra Oggetti
Le query GraphQL supportano relazioni tra oggetti. Puoi richiedere dati correlati in una singola query.
Esempio: Relazione Utente-Post
query {
user(id: "1") {
id
name
posts {
title
content
}
}
}
In questo esempio:
- La query richiede i dati di un utente e i relativi post.
- Per ogni post, vengono restituiti il titolo e il contenuto.
4. Passare Argomenti alle Query
Le query possono accettare argomenti che permettono di filtrare i risultati o recuperare informazioni specifiche.
Esempio di Query con Argomenti
query {
post(id: "101") {
id
title
author {
name
}
}
}
In questo esempio:
- La query richiede un post specifico utilizzando lâargomento
id
. - Per quel post, restituisce lâ
id
, iltitle
e il nome dellâauthor
.
Implementazione dei Risolutori di Query
Un risolutore di query è una funzione che restituisce i dati richiesti in una query. Ogni campo in una query GraphQL può avere un risolutore associato che gestisce il recupero dei dati.
Esempio di Risolutore per una Query Semplice
const resolvers = {
Query: {
user: async (parent, { id }, context) => {
// Recupera l'utente dal database utilizzando l'id
return await context.db.getUserById(id);
},
},
};
In questo esempio:
- La query
user
recupera un utente in base allâid
fornito. - Utilizza una funzione
getUserById
nel contesto per recuperare i dati dal database.
Esempio di Risolutore con Relazioni
Quando si lavora con dati relazionali, come utenti e post, è necessario scrivere risolutori per ogni relazione.
const resolvers = {
Query: {
user: async (parent, { id }, context) => {
return await context.db.getUserById(id);
},
},
User: {
posts: async (user, args, context) => {
return await context.db.getPostsByUserId(user.id);
},
},
};
In questo esempio:
- Il risolutore
user
restituisce un utente. - Il risolutore
posts
nel tipoUser
recupera tutti i post associati a quellâutente.
Ottimizzazione delle Query
In GraphQL, è importante ottimizzare le query per evitare problemi di prestazioni, soprattutto quando si gestiscono relazioni complesse o grandi volumi di dati.
1. Evitare il Problema N+1
Il problema N+1 si verifica quando una query che richiede dati correlati esegue troppe chiamate al database. Ad esempio, se recuperi 10 utenti e poi esegui una query separata per ogni post, avrai 1 query per ottenere gli utenti e N query per i post correlati (dove N è il numero di utenti).
Soluzione: Utilizzare DataLoader
DataLoader è uno strumento che consente di batchare e memorizzare in cache le richieste, riducendo il numero di query eseguite.
const DataLoader = require("dataloader");
const postLoader = new DataLoader(async (userIds) => {
const posts = await context.db.getPostsByUserIds(userIds);
return userIds.map((id) => posts.filter((post) => post.userId === id));
});
const resolvers = {
User: {
posts: (user, args, context) => postLoader.load(user.id),
},
};
In questo esempio:
- DataLoader raggruppa le richieste per i post degli utenti, riducendo il numero di query necessarie per recuperare i dati correlati.
2. Paginazione
La paginazione è essenziale per gestire query che possono restituire molti risultati. Senza paginazione, una query potrebbe restituire troppi dati, causando rallentamenti.
Esempio di Paginazione
query {
users(limit: 10, offset: 0) {
id
name
}
}
In questo esempio:
- La query
users
accetta due argomenti:limit
per limitare il numero di risultati eoffset
per specificare lâinizio del set di dati. - Questo consente di paginare i risultati, richiedendo solo 10 utenti alla volta.
3. Caching per Migliorare le Performance
Se i dati richiesti non cambiano frequentemente, puoi utilizzare il caching per ridurre il carico sul database e migliorare le performance delle query.
Esempio di Caching con Redis
const redis = require("redis");
const client = redis.createClient();
const resolvers = {
Query: {
user: async (parent, { id }, context) => {
const cacheKey = `user:${id}`;
const cachedUser = await client.get(cacheKey);
if (cachedUser) {
return JSON.parse(cachedUser);
}
const user = await context.db.getUserById(id);
if (user) {
client.set(cacheKey, JSON.stringify(user), "EX", 3600); // Cache per 1 ora
}
return user;
},
},
};
In questo esempio, i dati dellâutente vengono memorizzati nella cache per unâora, riducendo la necessitĂ di interrogare il database ogni volta.
Best Practices per la Scrittura di Query
1. Richiedere Solo i Dati Necessari
GraphQL ti consente di richiedere solo i dati di cui hai bisogno, quindi evita di fare overfetching (richiedere dati non necessari).
Esempio di Query Ottimizzata
query {
user(id: "1") {
name
email
}
}
In questo esempio, vengono richiesti solo name
e email
, ignorando gli altri campi.
2. Gestione degli Errori
Assicurati di gestire correttamente gli errori nei risolutori, restituendo messaggi significativi al client in caso di problemi.
const resolvers = {
Query: {
user: async (
parent,
{ id },
context
) => {
try {
const user = await context.db.getUserById(id);
if (!user) {
throw new Error("Utente non trovato");
}
return user;
} catch (error) {
throw new Error(`Errore nel recupero dell'utente: ${error.message}`);
}
},
},
};
3. Paginazione e Filtri
Implementa la paginazione e i filtri per evitare di sovraccaricare il server quando si recuperano elenchi di oggetti.
query {
users(limit: 10, offset: 20, filter: { name: "Alice" }) {
id
name
}
}
In questo esempio, viene applicato un filtro per ottenere solo utenti con nome âAliceâ, oltre a limitare il numero di risultati.
Conclusione
Le query in GraphQL offrono un modo potente e flessibile per recuperare i dati, consentendo ai client di richiedere esattamente ciò di cui hanno bisogno in unâunica richiesta. Scrivere query efficienti richiede una comprensione delle relazioni tra i dati, lâuso di tecniche di ottimizzazione come DataLoader per evitare il problema N+1, e lâimplementazione della paginazione e del caching per migliorare le prestazioni. Seguendo queste best practices, puoi costruire API GraphQL efficienti, scalabili e facili da usare.