Développement Web

GraphQL : API moderne pour applications web

10 دقائق للقراءة

Ce que vous saurez faire à la fin

  1. Monter un serveur GraphQL Apollo
  2. Schéma, résolveurs, mutations, subscriptions
  3. Résoudre N+1 avec DataLoader
  4. Sécuriser avec depth-limit et auth
  5. Consommer avec Apollo Client React

Étape 1 — Installation

npm install @apollo/server graphql

Étape 2 — Schéma et résolveurs

import { ApolloServer } from "@apollo/server";
import { startStandaloneServer } from "@apollo/server/standalone";

const typeDefs = `#graphql
  type Client {
    id: ID!
    nom: String!
    factures: [Facture!]!
  }
  type Facture {
    id: ID!
    montant: Float!
    client: Client!
  }
  type Query {
    client(id: ID!): Client
    clients: [Client!]!
  }
`;

const resolvers = {
  Query: {
    client: (_, { id }, { db }) => db.findClient(id),
    clients: (_, __, { db }) => db.allClients(),
  },
  Client: {
    factures: (parent, _, { db }) => db.facturesDuClient(parent.id),
  },
};

const server = new ApolloServer({ typeDefs, resolvers });
const { url } = await startStandaloneServer(server, { listen: { port: 4000 } });

Étape 3 — Mutations

const typeDefs = `#graphql
  input CreerClientInput {
    nom: String!
    ville: String
  }
  type Mutation {
    creerClient(input: CreerClientInput!): Client!
  }
`;

const resolvers = {
  Mutation: {
    creerClient: (_, { input }, { db }) => db.createClient(input),
  },
};

Étape 4 — DataLoader anti-N+1

npm install dataloader
import DataLoader from "dataloader";

function createLoaders(db) {
  return {
    clientLoader: new DataLoader(async (ids) => {
      const rows = await db.query("SELECT * FROM clients WHERE id = ANY($1)", [ids]);
      return ids.map(id => rows.find(r => r.id === id));
    }),
  };
}

// Context par requête
await startStandaloneServer(server, {
  context: async () => ({ db, ...createLoaders(db) }),
});

const resolvers = {
  Facture: {
    client: (parent, _, { clientLoader }) => clientLoader.load(parent.client_id),
  },
};

Étape 5 — Auth

import jwt from "jsonwebtoken";

context: async ({ req }) => {
  const token = req.headers.authorization?.replace("Bearer ", "");
  let user = null;
  if (token) {
    try { user = jwt.verify(token, process.env.JWT_SECRET); } catch {}
  }
  return { user };
}

const resolvers = {
  Query: {
    utilisateurs: (_, __, { user }) => {
      if (!user || user.role !== "admin") throw new Error("interdit");
      return db.listUsers();
    },
  },
};

Étape 6 — Subscriptions

import { PubSub } from "graphql-subscriptions";
const pubsub = new PubSub();

const typeDefs = `#graphql
  type Subscription {
    venteCreee: Facture!
  }
`;

const resolvers = {
  Mutation: {
    creerFacture: async (_, { input }) => {
      const f = await db.createFacture(input);
      pubsub.publish("VENTE_CREEE", { venteCreee: f });
      return f;
    },
  },
  Subscription: {
    venteCreee: {
      subscribe: () => pubsub.asyncIterator("VENTE_CREEE"),
    },
  },
};

Étape 7 — Sécurité

import depthLimit from "graphql-depth-limit";

const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [depthLimit(5)],
});

Étape 8 — Client React

npm install @apollo/client
import { ApolloClient, InMemoryCache, ApolloProvider, gql, useQuery } from "@apollo/client";

const client = new ApolloClient({
  uri: "http://localhost:4000/",
  cache: new InMemoryCache(),
});

function Clients() {
  const { data, loading } = useQuery(gql`query { clients { id nom } }`);
  if (loading) return <p>Chargement...</p>;
  return <ul>{data.clients.map(c => <li key={c.id}>{c.nom}</li>)}</ul>;
}

Étape 9 — Codegen TypeScript

npm install -D @graphql-codegen/cli @graphql-codegen/typescript-react-apollo

npx graphql-codegen

Étape 10 — Checklist prod

✓ Désactiver introspection en prod
✓ depthLimit + rate-limit + complexity
✓ DataLoader sur résolveurs relationnels
✓ Persisted queries
✓ Tracing Apollo Studio
✓ Auth obligatoire sur mutations
✓ Validation Zod des inputs

Etape 1 : Choisir GraphQL plutot que REST quand c est pertinent

GraphQL n’est pas un remplacant universel de REST : c’est un langage de requete adapte aux apps qui consomment beaucoup de donnees relationnelles depuis plusieurs sources. Pour une app mobile e-commerce a Dakar qui affiche produits, avis, stock et recommandations sur la meme page, GraphQL elimine les 4 a 6 appels REST necessaires en une seule requete. Pour un endpoint simple type ‘POST /commande’, REST reste plus direct et plus cacheable.

Regle de decision rapide : si vos clients front (web et mobile) ont des besoins de donnees differents qui changent souvent, GraphQL evite la multiplication des endpoints REST taille sur mesure. Si vos consumers sont stables et homogenes, restez sur REST et economisez la complexite serveur.

Etape 2 : Installer Apollo Server 4 sur Node.js 22 LTS

Apollo Server reste l’implementation Node de reference. La version 4 (sortie 2023, maintenue activement en 2026) tourne sur Node 22 LTS et expose un middleware Express, Fastify ou un standalone HTTP. Initialisez un projet propre :

npm init -y; npm install @apollo/server graphql express cors body-parser

Sortie attendue : 4 packages ajoutes au package.json. Verifiez que Node est en version 22.x avec node --version. Si vous etes en 18 ou 20, mettez a jour via nvm car Apollo Server 4 abandonne le support des versions inferieures a 18 et plusieurs depandances modernes exigent 22.

Etape 3 : Definir le schema GraphQL pour un cas reel

Le schema decrit les types et les operations disponibles. Pour un mini-blog avec articles et auteurs, le schema typique fait 30 lignes :

type Author { id: ID! name: String! articles: [Article!]! } type Article { id: ID! title: String! content: String author: Author! publishedAt: String } type Query { article(id: ID!): Article articles(limit: Int = 10): [Article!]! authors: [Author!]! } type Mutation { createArticle(title: String!, content: String!, authorId: ID!): Article! }

Ce schema expose 3 queries et 1 mutation. Le client peut demander seulement title et author { name } sans recevoir le contenu lourd, ce qui economise de la bande passante critique en 4G ouest-africaine.

Etape 4 : Implementer les resolvers connectes a une base de donnees

Les resolvers sont les fonctions qui repondent aux queries. Branchez Postgres via Prisma, ou MySQL via Knex. Pour un projet rapide, Prisma reduit le temps d’integration a 20 minutes :

npx prisma init; // editez schema.prisma puis npx prisma generate; npx prisma migrate dev --name init

Cote resolvers, vous appelez les methodes Prisma generes :

const resolvers = { Query: { articles: (_, { limit }) => prisma.article.findMany({ take: limit, include: { author: true } }), article: (_, { id }) => prisma.article.findUnique({ where: { id } }) }, Article: { author: (parent) => prisma.author.findUnique({ where: { id: parent.authorId } }) } };

Le resolver Article.author est appele uniquement si le client le demande dans sa query, evitant des jointures inutiles. Cette demande paresseuse est un gain net face a un endpoint REST qui retourne toujours l’auteur en payload.

Etape 5 : Demarrer le serveur et tester avec Apollo Sandbox

Branchez Apollo Server sur Express et exposez le port 4000 :

const server = new ApolloServer({ typeDefs, resolvers }); await server.start(); app.use('/graphql', cors(), bodyParser.json(), expressMiddleware(server)); app.listen(4000);

Lancez node index.js et ouvrez http://localhost:4000/graphql dans le navigateur. Apollo Sandbox s’affiche : un IDE web pour tester vos queries en temps reel, avec autocompletion basee sur le schema. C’est nettement plus productif que Postman pour un travail GraphQL au jour le jour.

Etape 6 : Securiser l API avec authentification JWT et rate limiting

GraphQL expose un seul endpoint, ce qui simplifie le rate limiting mais complique le contole d’acces fin par operation. Implementez le pattern context : decodez le JWT a chaque requete et passez l’utilisateur aux resolvers.

const server = new ApolloServer({ typeDefs, resolvers, context: ({ req }) => ({ user: verifyJwt(req.headers.authorization) }) });

Dans chaque resolver sensible, verifiez context.user avant d’executer. Ajoutez graphql-rate-limit via une directive de schema pour bloquer les clients qui spamment. Sur un VPS Hetzner CX22 expose au public, sans ces deux protections, votre API tombe en moins de 24 heures sous les scans automatises.

Etape 7 : Eviter le N+1 avec DataLoader

Le piege classique de GraphQL : une query qui demande 100 articles avec leur auteur declenche 1 requete pour les articles + 100 requetes pour chaque auteur. C’est le N+1, et il tue les performances. La solution est dataloader, qui regroupe les appels en un seul batch.

const authorLoader = new DataLoader(async (ids) => { const authors = await prisma.author.findMany({ where: { id: { in: ids } } }); return ids.map(id => authors.find(a => a.id === id)); });

Branchez le loader dans le context, puis remplacez le resolver Article.author par (parent) => context.authorLoader.load(parent.authorId). Vous passez de 101 requetes SQL a 2, soit un gain de latence x50 sur les listings denses.

Etape 8 : Mettre en cache les queries cote client avec Apollo Client

Apollo Client (cote frontend React, Vue ou React Native) cache automatiquement les queries en memoire. Configurez-le avec un InMemoryCache et activez la persistance via apollo3-cache-persist pour conserver le cache entre sessions, ce qui acceler considerablement le redemarrage de l’app sur 3G.

const client = new ApolloClient({ uri: 'https://api.example.com/graphql', cache: new InMemoryCache() });

Pour les apps mobiles ouest-africaines, ce cache est decisif : un utilisateur a Saint-Louis qui revient sur l’app voit les donnees en moins de 200 ms au lieu d’attendre 2 secondes le re-fetch reseau, et continue d’utiliser l’app meme en cas de coupure ponctuelle de connexion 4G.

Etape 9 : Deployer en production sur un VPS Hetzner avec Caddy en frontal

Pour la prod, lancez votre serveur Apollo derriere Caddy en reverse proxy avec HTTPS automatique. La stack tient sur un CX22 (4,50 EUR/mois) jusqu’a environ 100 requetes/seconde. Pour un trafic plus eleve, passez en CX32 (8 Go RAM) et activez le clustering Node via PM2.

Voir aussi notre categorie DevOps et la categorie Backend sur le même thème sur l’optimisation Node.js et les architectures distribuees.

Etape 10 : Monitorer les queries lentes et ajuster

Installez @apollo/server-plugin-response-cache pour cacher les queries publiques cote serveur, et branchez Apollo Studio (gratuit pour 1 utilisateur) pour visualiser les queries reelles, leurs temps d’execution et identifier les goulots. Sur un projet a 6 mois, vous decouvrez souvent qu’une seule query mal ecrite represente 40 % du temps CPU et qu’un index manquant en base la divise par 10.

Etape 11 : Versionner un schema GraphQL sans casser les clients

Contrairement a REST ou on multiplie /v1, /v2, GraphQL impose la regle inverse : on ajoute, on ne supprime jamais. Pour deprecier un champ, marquez-le avec @deprecated(reason: "Use newField"). Les clients voient l’avertissement dans Apollo Sandbox et peuvent migrer a leur rythme. Apres 6 mois sans usage (mesurable via Apollo Studio), vous supprimez physiquement le champ sans risque.

type Article { title: String! oldField: String @deprecated(reason: "Use newField") newField: String }

Cette discipline evite les ruptures de contrat avec les apps mobiles deployees chez les utilisateurs : un client a Bamako qui n’a pas mis a jour son APK depuis 3 mois continue de fonctionner pendant que vous faites evoluer le backend, ce qui est essentiel quand les mises a jour Play Store sont lentes a se propager.

Etape 12 : Recap et bonnes pratiques pour aller en prod

Reprenez avant le go-live : schema complet et documente, resolvers couverts par tests unitaires (Vitest ou Jest), DataLoader actif sur toutes les relations, JWT verifie en context, rate limiting global, cache reponse cote serveur sur queries publiques, monitoring Apollo Studio branche, sauvegardes Postgres planifiees, deploiement derriere Caddy avec HTTPS. Cette checklist en 9 points distingue un POC bricole d’une API GraphQL prete a tenir une charge reelle d’app mobile en production.

Etape 13 : Subscriptions temps reel via WebSocket

Pour des cas d’usage temps reel (notifications, chat, tableau de bord live), GraphQL expose les subscriptions via WebSocket. Apollo Server 4 utilise graphql-ws comme transport recommande, le predecesseur subscriptions-transport-ws n’est plus maintenu. Branchez le sur le meme serveur HTTP et exposez un type Subscription dans le schema.

type Subscription { newArticle: Article! }

Cote client, Apollo Client souscrit avec useSubscription en React et recoit les updates pousses par le serveur. Pour un dashboard de monitoring d’une boutique e-commerce a Abidjan qui veut voir les nouvelles commandes en temps reel, c’est plus efficient que du polling REST toutes les 5 secondes qui sature inutilement le 4G mobile.

Etape 14 : Tarification serveur et estimation budgetaire

Pour un projet GraphQL prod en Afrique de l’Ouest, le budget serveur typique tient sur 12 EUR mensuels (environ 7 870 FCFA) : un VPS Hetzner CX22 a 4,50 EUR pour Apollo, un CX22 supplementaire pour Postgres, et un volume de 20 Go pour les sauvegardes. Cette base supporte une app mobile a 5 000 utilisateurs actifs quotidiens sans broncher, avec la marge pour scaler verticalement avant de passer en architecture distribuee.

Etape 15 : Documenter le schema pour l equipe

Apollo Sandbox affiche automatiquement la documentation generee depuis le schema, mais ajoutez des descriptions textuelles via la syntaxe triple guillemet sur chaque type et chaque champ critique. Vos developpeurs front comprennent immediatement ce que retourne article.publishedAt (timestamp ISO 8601) sans devoir lire le code resolver, ce qui accelere l’integration des nouveaux membres de l’equipe a Dakar ou Cotonou.

مشاركة