Retour au cours

backend / graphql

Client GraphQL : Apollo Client et fetch

Leçon 131 exercice

Explication

Ce que vous allez apprendre

  • Interroger une API GraphQL avec un simple fetch natif, sans dépendance
  • Comprendre ce qu'Apollo Client apporte par-dessus une requête HTTP brute : cache normalisé, hooks
  • Mettre en place un authLink pour centraliser l'ajout du token d'authentification
  • Utiliser useQuery et useMutation dans un composant React
  • Mettre à jour le cache Apollo après une mutation, sans requête réseau supplémentaire

Dans quel contexte ?

Une équipe frontend React construit une liste de produits avec un bouton "Ajouter un produit". Elle a besoin d'afficher un état de chargement pendant que la requête part, de gérer les erreurs réseau proprement, et surtout de faire apparaître immédiatement le nouveau produit dans la liste après sa création, sans recharger toute la page ni refaire une requête réseau complète.

D'abord, la solution la plus simple : fetch natif

Rien n'empêche d'appeler une API GraphQL avec un fetch classique : une requête POST, un corps JSON contenant query et variables, et une réponse à parser. Pour un script ponctuel ou une intégration très simple, c'est largement suffisant et ne demande aucune dépendance supplémentaire.

Une fois cette base comprise, la limite apparaît vite

Dès qu'une application grandit — plusieurs composants qui affichent la même donnée, besoin d'un état de chargement cohérent, mise à jour de l'interface après une mutation — gérer tout ça à la main avec fetch devient répétitif et source de bugs (des composants qui affichent des données obsolètes après une modification ailleurs dans l'app, par exemple).

Ensuite, Apollo Client répond à ce besoin avec un cache normalisé

Apollo Client stocke chaque objet reçu dans un cache indexé par son id et son __typename (le nom du type GraphQL). Concrètement, si un Produit avec l'id "42" est reçu deux fois par deux requêtes différentes, Apollo Client fusionne automatiquement les deux résultats en un seul objet en cache : tous les composants qui affichent ce produit restent cohérents entre eux, sans code supplémentaire.

ApprocheCache automatiqueHooks déclaratifs (loading/error/data)Complexité d'installation
fetch natifNonNon, à gérer manuellementAucune
Apollo ClientOui, normalisé par id+typeOui (useQuery, useMutation)Une dépendance, une configuration de client

Il reste un problème récurrent : où mettre l'authentification ?

Répéter l'ajout du header Authorization dans chaque appel serait source d'oublis. Un authLink, branché une seule fois lors de la création du client (authLink.concat(httpLink)), intercepte chaque requête sortante pour y ajouter automatiquement le token — un pattern de middleware très proche de ce que tu connais côté serveur.

Prérequis

Cette leçon suppose des bases en React (composants fonctionnels, hooks useState) : useQuery et useMutation s'utilisent comme n'importe quel autre hook React.

Enfin, le cas le plus délicat : mettre à jour le cache après une mutation

Après avoir créé un produit, deux options existent : soit redemander toute la liste au serveur (simple, mais un aller-retour réseau de plus), soit modifier directement le cache local avec cache.modify et cache.writeFragment pour y insérer le nouveau produit. La seconde option, plus technique, offre une interface instantanément à jour sans latence réseau supplémentaire.

Piège fréquent

Oublier de fournir id et __typename dans la sélection d'une query est l'erreur la plus fréquente avec Apollo Client : sans ces deux informations, le cache normalisé ne peut identifier l'objet et ne peut donc ni le dédupliquer ni le mettre à jour correctement après une mutation.

Maintenant que tu sais consommer une API GraphQL des deux côtés, la prochaine leçon aborde un sujet incontournable dès qu'une API est exposée publiquement : comment se protéger contre des requêtes malveillantes ou simplement trop coûteuses.

Commandes & code

Client GraphQL : Apollo Client et fetch

javascript
// Le plus simple : fetch natif, sans librairie -- suffisant pour des cas simples
async function obtenirProduit(id) {
  const reponse = await fetch('https://api.exemple.com/graphql', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${token}`,
    },
    body: JSON.stringify({
      query: `query ObtenirProduit($id: ID!) { produit(id: $id) { nom prix } }`,
      variables: { id },
    }),
  });
  const { data, errors } = await reponse.json();
  if (errors) throw new Error(errors[0].message);
  return data.produit;
}
javascript
// Apollo Client : cache normalisé, gestion d'état, hooks React intégrés
import { ApolloClient, InMemoryCache, createHttpLink, gql } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';

const httpLink = createHttpLink({ uri: 'https://api.exemple.com/graphql' });

// Middleware d'authentification : ajoute le token à chaque requête sortante
const authLink = setContext((_, { headers }) => {
  const token = localStorage.getItem('token');
  return { headers: { ...headers, authorization: token ? `Bearer ${token}` : '' } };
});

const client = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache(),   // cache normalisé par id d'objet, dédoublonne automatiquement
});
jsx
// Hooks React d'Apollo : useQuery gère loading/error/data automatiquement
import { useQuery, useMutation, gql } from '@apollo/client';

const OBTENIR_PRODUITS = gql`
  query ObtenirProduits($limite: Int) {
    produits(limite: $limite) {
      id
      nom
      prix
    }
  }
`;

const CREER_PRODUIT = gql`
  mutation CreerProduit($input: CreerProduitInput!) {
    creerProduit(input: $input) {
      id
      nom
    }
  }
`;

function ListeProduits() {
  const { loading, error, data } = useQuery(OBTENIR_PRODUITS, { variables: { limite: 10 } });

  const [creerProduit] = useMutation(CREER_PRODUIT, {
    // Met à jour le cache Apollo après la mutation sans refaire de requête réseau
    update(cache, { data: { creerProduit } }) {
      cache.modify({
        fields: {
          produits(produitsExistants = []) {
            const nouvelleRef = cache.writeFragment({
              data: creerProduit,
              fragment: gql`fragment NouveauProduit on Produit { id nom prix }`,
            });
            return [...produitsExistants, nouvelleRef];
          },
        },
      });
    },
  });

  if (loading) return <p>Chargement...</p>;
  if (error) return <p>Erreur: {error.message}</p>;

  return (
    <ul>
      {data.produits.map((p) => (
        <li key={p.id}>{p.nom} — {p.prix} €</li>
      ))}
    </ul>
  );
}

Résumé

  • fetch brut suffit pour des besoins simples ; Apollo Client apporte cache normalisé et hooks déclaratifs.
  • Le cache Apollo dédoublonne automatiquement les objets par id + __typename, évitant les incohérences UI.
  • cache.modify / cache.writeFragment mettent à jour le cache après une mutation sans requête réseau supplémentaire.
  • Un authLink centralise l'ajout du token à chaque requête, plutôt que de le répéter dans chaque appel.

Exercices pratiques

1 disponible
1

Mission : réparer une liste de produits qui n'affiche pas les nouveaux ajouts

Objectif : Diagnostiquer un bug classique de cache Apollo après une mutation, et corriger la mise à jour du cache sans requête réseau supplémentaire.

Contexte

Après une mutation creerProduit réussie (le serveur répond bien avec les données du nouveau produit), la liste de produits affichée ailleurs dans l'application via useQuery ne se met pas à jour tant que l'utilisateur ne recharge pas la page. L'équipe soupçonne un problème de cache Apollo plutôt qu'un bug serveur.

Résoudre l’exercice →