backend / graphql
Client GraphQL : Apollo Client et fetch
Explication
Ce que vous allez apprendre
- Interroger une API GraphQL avec un simple
fetchnatif, sans dépendance - Comprendre ce qu'Apollo Client apporte par-dessus une requête HTTP brute : cache normalisé, hooks
- Mettre en place un
authLinkpour centraliser l'ajout du token d'authentification - Utiliser
useQueryetuseMutationdans 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.
| Approche | Cache automatique | Hooks déclaratifs (loading/error/data) | Complexité d'installation |
|---|---|---|---|
fetch natif | Non | Non, à gérer manuellement | Aucune |
| Apollo Client | Oui, normalisé par id+type | Oui (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
// 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;
}// 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
});// 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é
fetchbrut 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.writeFragmentmettent à jour le cache après une mutation sans requête réseau supplémentaire.- Un
authLinkcentralise l'ajout du token à chaque requête, plutôt que de le répéter dans chaque appel.
Exercices pratiques
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.