Scrittura di Mutations in GraphQL: Guida Completa
In GraphQL, le mutations sono il modo principale per eseguire operazioni di scrittura come creare, aggiornare o eliminare dati. A differenza delle query, che recuperano informazioni, le mutations modificano lo stato dei dati nel backend. In questo articolo esploreremo come scrivere mutations in GraphQL, esamineremo la loro struttura e implementazione, e vedremo esempi pratici per gestire in modo efficiente le operazioni di scrittura in un’applicazione GraphQL.
Cos’è una Mutation in GraphQL?
Le mutations sono simili alle query in GraphQL, ma con una differenza fondamentale: mentre le query sono utilizzate per leggere i dati, le mutations vengono utilizzate per modificarli. Ogni mutation può eseguire operazioni come la creazione di nuovi record, l’aggiornamento di dati esistenti o l’eliminazione di elementi.
Esempio di Mutation di Base
mutation {
createUser(name: "Alice", email: "alice@example.com") {
id
name
email
}
}
In questo esempio, la mutation createUser
crea un nuovo utente con un nome e un’email. Restituisce i dati dell’utente appena creato, inclusi id
, name
e email
.
Struttura di una Mutation
Una mutation in GraphQL ha una struttura simile a una query. Viene dichiarata nello schema con il tipo Mutation
, definisce gli argomenti necessari per l’operazione, e specifica il tipo di dato che viene restituito.
Definizione dello Schema per le Mutations
Le mutations sono definite nel tipo Mutation
all’interno dello schema GraphQL, in modo simile a come vengono definite le query.
Esempio di Schema con Mutation
type Mutation {
createUser(name: String!, email: String!): User!
updateUser(id: ID!, name: String, email: String): User!
deleteUser(id: ID!): Boolean
}
type User {
id: ID!
name: String!
email: String!
}
In questo schema:
- La mutation
createUser
accettaname
eemail
come argomenti e restituisce un oggettoUser
. - La mutation
updateUser
aggiorna i campiname
eemail
di un utente esistente e restituisce l’utente aggiornato. - La mutation
deleteUser
elimina un utente in base al suoid
e restituisce un valore booleano (true
ofalse
) per indicare se l’operazione è riuscita.
Implementazione di Mutations in GraphQL
I risolutori per le mutations gestiscono la logica di backend per le operazioni di scrittura. Un risolutore di mutation segue lo stesso approccio dei risolutori di query, ma con la differenza che esegue operazioni che modificano i dati nel backend.
1. Implementazione della Mutation createUser
La mutation createUser
viene utilizzata per creare un nuovo utente. Ecco come implementarla in un risolutore:
const resolvers = {
Mutation: {
createUser: async (parent, { name, email }, context) => {
// Crea un nuovo utente nel database
const newUser = { id: Date.now().toString(), name, email };
context.db.createUser(newUser); // Funzione che salva l'utente nel database
return newUser;
},
},
};
In questo esempio:
- Il risolutore
createUser
accetta gli argomentiname
eemail
dal client. - Viene creato un nuovo oggetto
User
con unid
generato e i valori forniti. - La funzione
context.db.createUser
salva l’utente nel database e restituisce il nuovo utente.
2. Implementazione della Mutation updateUser
La mutation updateUser
viene utilizzata per aggiornare i dettagli di un utente esistente.
const resolvers = {
Mutation: {
updateUser: async (parent, { id, name, email }, context) => {
// Verifica se l'utente esiste
const user = await context.db.getUserById(id);
if (!user) {
throw new Error("Utente non trovato");
}
// Aggiorna i campi dell'utente
user.name = name || user.name;
user.email = email || user.email;
// Salva l'utente aggiornato nel database
await context.db.updateUser(user);
return user;
},
},
};
In questo esempio:
- Il risolutore
updateUser
recupera l’utente dal database utilizzando l’id
fornito. - I campi
name
eemail
vengono aggiornati solo se sono forniti dal client. - L’utente aggiornato viene restituito al client.
3. Implementazione della Mutation deleteUser
La mutation deleteUser
viene utilizzata per eliminare un utente esistente.
const resolvers = {
Mutation: {
deleteUser: async (parent, { id }, context) => {
const user = await context.db.getUserById(id);
if (!user) {
throw new Error("Utente non trovato");
}
// Cancella l'utente dal database
const success = await context.db.deleteUser(id);
return success ? true : false;
},
},
};
In questo esempio:
- La mutation
deleteUser
cerca l’utente da eliminare utilizzando l’id
. - Se l’utente viene trovato, viene eliminato dal database e la mutation restituisce
true
. Se l’utente non esiste, viene generato un errore.
Migliorare le Mutations: Best Practices
1. Validazione degli Input
Prima di eseguire una mutation, è essenziale validare gli input forniti dal client per evitare errori e garantire che i dati siano nel formato corretto.
Esempio di Validazione dell’Email
const resolvers = {
Mutation: {
createUser: async (parent, { name, email }, context) => {
// Verifica che l'email sia valida
if (!email.includes("@")) {
throw new Error("Indirizzo email non valido");
}
const newUser = { id: Date.now().toString(), name, email };
await context.db.createUser(newUser);
return newUser;
},
},
};
2. Gestione degli Errori
Implementa una solida gestione degli errori per restituire messaggi di errore chiari in caso di problemi, come l’assenza di dati o errori di connessione al database.
const resolvers = {
Mutation: {
updateUser: async (parent, { id, name, email }, context) => {
try {
const user = await context.db.getUserById(id);
if (!user) {
throw new Error("Utente non trovato");
}
user.name = name || user.name;
user.email = email || user.email;
await context.db.updateUser(user);
return user;
} catch (error) {
throw new Error(
`Errore durante l'aggiornamento dell'utente: ${error.message}`
);
}
},
},
};
3. Mutazioni Ottimistiche
Le mutazioni ottimistiche consentono di aggiornare l’interfaccia utente immediatamente, prima che il server confermi il cambiamento. Questa tecnica migliora l’esperienza utente in applicazioni con latenza elevata.
Esempio di Mutation Ottimistica con Apollo Client
import { gql, useMutation } from "@apollo/client";
const CREATE_USER = gql`
mutation CreateUser($name: String!, $email: String!) {
createUser(name: $name, email: $email) {
id
name
email
}
}
`;
const [createUser] = useMutation(CREATE_USER);
const handleCreateUser = () => {
createUser({
variables: { name: "Alice", email: "alice@example.com" },
optimisticResponse: {
createUser: {
id: "temp-id",
name: "Alice",
email: "alice@example.com",
},
},
});
};
In questo esempio, Apollo Client aggiorna l’interfaccia utente in modo ottimistico, mostrando l’utente prima di ricevere la risposta dal server.
4. Gestione delle Transazioni
Se una mutation comporta piĂą operazioni che devono essere eseguite in modo atomico (tutte o nessuna devono essere completate), utilizza le transazioni.
Esempio di Transazione con Database
const resolvers = {
Mutation: {
createOrder: async (parent, { input }, context) => {
const transaction = await context.db.startTransaction();
try {
const order = await context.db.createOrder(input.orderDetails, {
transaction,
});
await context.db.updateInventory(input.products, { transaction });
await transaction.commit(); // Conferma la transazione
return order;
} catch (error) {
await transaction.rollback(); // Annulla la transazione in caso di errore
throw new Error("Errore durante la creazione dell'ordine");
}
},
},
};
5. Esecuzione di Mutazioni Multiple in Sequenza
In alcuni casi, potrebbe essere necessario eseguire piĂą mutations in sequenza. In questi casi, GraphQL supporta la concatenazione di mutations multiple nella stessa richiesta.
Esempio di Mutazioni Multiple
mutation {
createUser(name: "Alice", email: "alice@example.com") {
id
name
}
createPost(
title: "Nuovo Post"
content: "Questo è il contenuto"
authorId: "1"
) {
id
title
content
}
}
In questo esempio, vengono eseguite una createUser
e una createPost
nello stesso ciclo di richiesta.
Conclusione
Le mutations in GraphQL sono strumenti potenti per gestire operazioni di scrittura come la creazione, l’aggiornamento e la cancellazione di dati. Implementare correttamente le mutations richiede una solida gestione degli input, la validazione dei dati e una robusta gestione degli errori. Le mutazioni ottimistiche e l’uso delle transazioni possono migliorare ulteriormente l’esperienza utente e la robustezza dell’applicazione.