Definizione di Mutations in GraphQL: Guida Completa

Le mutations in GraphQL sono operazioni che permettono di creare, aggiornare o eliminare dati nel sistema. A differenza delle query, che sono utilizzate per leggere i dati, le mutations vengono utilizzate per modificarli, rendendole essenziali per le applicazioni che richiedono operazioni di scrittura. In questa guida, esploreremo come definire e implementare mutations in GraphQL, con esempi pratici e best practices per garantire che siano scalabili e manutenibili.
Cosa Sono le Mutations in GraphQL?
Le mutations sono operazioni che eseguono modifiche sui dati del sistema. Ogni mutation può essere paragonata a un’operazione HTTP POST, PUT, PATCH o DELETE in un’API REST. La differenza principale rispetto alle query è che le mutations possono avere effetti collaterali, come l’aggiornamento di un database o l’invio di una notifica.
1. Definire una Mutation nello Schema GraphQL
1.1. Creare lo Schema di Base
Per iniziare, definiamo uno schema di base che include tipi e una mutation semplice. Supponiamo di avere un’applicazione di blog, con tipi Post e Author.
Crea un file schema.js:
const { gql } = require("apollo-server");
const typeDefs = gql`
type Post {
id: ID!
title: String!
content: String!
author: Author!
}
type Author {
id: ID!
name: String!
posts: [Post!]!
}
type Query {
posts: [Post!]!
post(id: ID!): Post
}
type Mutation {
createPost(title: String!, content: String!, authorId: ID!): Post!
}
`;
module.exports = typeDefs;
1.2. Definire una Mutation
Nell’esempio sopra, abbiamo definito una mutation chiamata createPost che accetta tre argomenti: title, content, e authorId. Questa mutation crea un nuovo post associato a un autore esistente.
type Mutation {
createPost(title: String!, content: String!, authorId: ID!): Post!
}
1.3. Tipi Input per Mutations Complesse
Per le mutations che richiedono più parametri, è consigliabile utilizzare tipi input per migliorare la leggibilità e la manutenibilità.
input PostInput {
title: String!
content: String!
authorId: ID!
}
type Mutation {
createPost(input: PostInput!): Post!
}
2. Implementare i Resolvers per le Mutations
I resolvers sono funzioni che eseguono la logica delle mutations, come la creazione di un nuovo record nel database.
Crea un file resolvers.js:
const { PubSub } = require("apollo-server");
const pubsub = new PubSub();
let posts = [];
let authors = [
{ id: "1", name: "Alice" },
{ id: "2", name: "Bob" },
];
const resolvers = {
Query: {
posts: () => posts,
post: (parent, args) => posts.find((post) => post.id === args.id),
},
Mutation: {
createPost: (parent, { input }) => {
const author = authors.find((author) => author.id === input.authorId);
if (!author) {
throw new Error("Author not found");
}
const post = {
id: `${posts.length + 1}`,
title: input.title,
content: input.content,
author: author,
};
posts.push(post);
pubsub.publish("POST_CREATED", { postCreated: post });
return post;
},
},
Author: {
posts: (author) => posts.filter((post) => post.author.id === author.id),
},
};
module.exports = resolvers;
In questo esempio, il resolver createPost:
- Trova l’autore nel sistema.
- Crea un nuovo oggetto
Post. - Aggiunge il nuovo post all’array
posts. - Restituisce il post creato.
3. Gestione degli Errori nelle Mutations
Le mutations possono fallire per vari motivi, come la mancanza di autorizzazioni o la violazione di vincoli sui dati. È essenziale gestire correttamente questi errori.
Esempio di Gestione degli Errori
Nel resolver createPost, abbiamo già gestito un errore quando l’autore non viene trovato. In caso di errori più complessi, potresti voler restituire errori più dettagliati utilizzando ApolloError.
const { ApolloError } = require("apollo-server");
const resolvers = {
Mutation: {
createPost: (parent, { input }) => {
const author = authors.find((author) => author.id === input.authorId);
if (!author) {
throw new ApolloError("Author not found", "AUTHOR_NOT_FOUND");
}
// Logica per creare il post...
return post;
},
},
};
4. Best Practices per Definire Mutations
4.1. Utilizzare Tipi Input
Come accennato, utilizzare tipi input per mutations complesse rende le operazioni più leggibili e manutenibili.
input UpdatePostInput {
id: ID!
title: String
content: String
}
type Mutation {
updatePost(input: UpdatePostInput!): Post!
}
4.2. Restituire i Dati Aggiornati
Dopo una mutation, è utile restituire i dati aggiornati. Questo permette al client di aggiornare l’interfaccia utente senza eseguire ulteriori query.
type Mutation {
updatePost(id: ID!, title: String, content: String): Post!
}
4.3. Gestire le Relazioni in Mutations
Assicurati che le mutations gestiscano correttamente le relazioni tra i dati, come l’aggiornamento dei riferimenti tra un post e un autore.
4.4. Documentare le Mutations
Aggiungi descrizioni chiare alle mutations per facilitare la comprensione da parte degli sviluppatori che utilizzano la tua API.
"""
Crea un nuovo post associato a un autore esistente.
"""
type Mutation {
createPost(title: String!, content: String!, authorId: ID!): Post!
}
5. Testare le Mutations
5.1. Test Manuale
Usa Apollo Studio Explorer o GraphQL Playground per testare manualmente le mutations. Assicurati di testare tutti i casi, inclusi i casi limite e le condizioni di errore.
5.2. Test Automatizzati
Scrivi test automatizzati per le mutations utilizzando framework come Jest o Mocha. Testa le operazioni con vari input e verifica che i risultati siano corretti.
Conclusione
Definire mutations in GraphQL è fondamentale per creare API potenti e flessibili che permettono ai client di interagire e modificare i dati nel sistema. Seguendo le best practices e implementando correttamente i resolvers, puoi garantire che le mutations siano sicure, efficienti e facili da mantenere. Con l’esperienza acquisita in questa guida, sei pronto a definire mutations che migliorano l’usabilità e la scalabilità della tua API GraphQL.