🚀 Nuova versione beta disponibile! Feedback o problemi? Contattaci

Scrittura di Mutations in GraphQL: Guida Completa

Codegrind Team•Sep 03 2024

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 accetta name e email come argomenti e restituisce un oggetto User.
  • La mutation updateUser aggiorna i campi name e email di un utente esistente e restituisce l’utente aggiornato.
  • La mutation deleteUser elimina un utente in base al suo id e restituisce un valore booleano (true o false) 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 argomenti name e email dal client.
  • Viene creato un nuovo oggetto User con un id 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 e email 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.