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.