Ce que vous saurez faire à la fin
- Comprendre les différences fondamentales entre REST et GraphQL : architecture, requêtes, types, performance et complexité.
- Identifier en 5 questions si votre projet PME doit choisir REST, GraphQL, ou les deux en parallèle.
- Créer un serveur GraphQL minimal en Node.js avec Apollo Server et le tester avec GraphQL Playground.
- Mettre en place les protections obligatoires d’une API GraphQL en production : query complexity, depth limit, authentification.
- Comparer concrètement les performances, le coût d’intégration et la maintenabilité sur un cas e-commerce sénégalais.
Durée : 3h. Pré-requis : Node.js 20+ installé, notions de base REST (lire l’article précédent), un éditeur de code (VS Code), navigateur web moderne, budget total : 0 FCFA (tout est gratuit pour le dev).
Étape 1 — Le contexte 2026 en une page
REST (Representational State Transfer) date de 2000, GraphQL a été créé par Facebook en 2012 et open-sourcé en 2015. En 2026, REST domine encore 75% des APIs publiques (Stripe, GitHub REST, Twilio, Wave, Orange Money), tandis que GraphQL a conquis 35% des nouvelles applications complexes (Shopify, GitHub GraphQL, Atlassian, Hashnode). En Afrique, la quasi-totalité des fintechs utilisent REST pour leur API publique car c’est ce que les développeurs locaux maîtrisent.
La question n’est pas « lequel est meilleur » mais « lequel est adapté à votre contexte ». Les deux peuvent coexister : votre backoffice interne en GraphQL, votre API publique partenaires en REST. Choisir mal coûte 6 à 18 mois de retard sur un produit, choisir bien fait gagner 30% de temps de développement.
Étape 2 — REST en une requête, GraphQL en une requête
CAS : afficher la fiche d'un client avec ses 3 dernières factures
et le total payé en 2026.
REST CLASSIQUE (3 appels nécessaires) :
GET /api/v1/clients/42
-> {id:42, nom:"Mamadou", telephone:"+221...", ville:"Dakar"}
GET /api/v1/clients/42/factures?limit=3&sort=-date
-> [{id:101,...}, {id:99,...}, {id:97,...}]
GET /api/v1/clients/42/stats?annee=2026
-> {total_paye:1850000, nb_factures:24}
Total : 3 requêtes HTTP, 3 round-trips réseau (300 ms cumulés depuis Dakar)
GRAPHQL (1 appel) :
POST /graphql
{
client(id: 42) {
nom
telephone
ville
factures(limit: 3, sort: "-date") {
id
numero
montant
date
}
stats(annee: 2026) {
totalPaye
nbFactures
}
}
}
Total : 1 requête HTTP, 1 round-trip (100 ms depuis Dakar)
Sur un réseau 3G congestionné typique de Sandaga ou Pikine, économiser 2 round-trips fait passer une page de 1,5 s à 0,5 s. Pour une app mobile, c’est la différence entre conversion et abandon.
Étape 3 — Tableau comparatif : 12 critères clés
CRITÈRE REST GRAPHQL
-----------------------------------------------------------------
Format réponse JSON fixe par route JSON sur mesure
Nombre d'endpoints Beaucoup (dizaines) Un seul (/graphql)
Versioning /api/v1/, /api/v2/ Évolution de schéma
Sur-fetching Fréquent Impossible
Sous-fetching Fréquent Impossible
Cache HTTP natif Excellent (GET) Difficile (POST)
Outillage CDN Cloudflare optimisé Outillage limité
Courbe apprentissage 1 semaine 1 mois
Maturité écosystème 20+ ans 10 ans
Support partenaires Afrique Quasi 100% ~30%
Schéma fortement typé Optionnel (OpenAPI) Natif (SDL)
Risque DDoS facile Faible Élevé sans protection
-----------------------------------------------------------------
Ce tableau résume la décision : si votre clientèle est composée de développeurs senior maîtrisant GraphQL avec des données très imbriquées (réseau social, dashboard analytics), GraphQL gagne. Si vous ouvrez une API à des intégrateurs hétérogènes (transporteurs, comptables, banques), REST gagne.
Étape 4 — Quand choisir REST en 2026
CHOISIR REST SI :
1. Vous ouvrez votre API à des PARTENAIRES EXTERNES
(banques, télécoms, grandes entreprises au Sénégal)
-> ils savent tous faire du REST, peu connaissent GraphQL
2. Vous avez beaucoup de fichiers volumineux
(upload PDF de factures, photos de produits, vidéos)
-> multipart/form-data REST est mieux supporté
3. Vous voulez maximiser le cache CDN (Cloudflare)
-> GET REST est cacheable nativement, pas POST GraphQL
4. Vous êtes une PME avec une équipe junior à Dakar
-> recrutement plus facile sur REST
5. Vous avez un domaine simple (CRUD classique)
-> clients, factures, produits sans surrelations
6. Vos clients sont des intégrateurs ERP, comptabilité, paiement
-> outils existants (Postman, Zapier) maîtrisent REST
EXEMPLES SÉNÉGALAIS REST :
- Wave Money API
- Orange Money API
- YAS Money (ex-Free Money) API
- Sénélec API EDS
- Air Sénégal réservations
Étape 5 — Quand choisir GraphQL en 2026
CHOISIR GRAPHQL SI :
1. Vous avez une APP MOBILE (Android, iOS, Flutter, React Native)
-> économiser des round-trips en zone réseau faible
2. Votre dashboard backoffice a des écrans très RICHES
-> jusqu'à 8 ressources liées par page
3. Vous itérez vite sur le frontend
-> le front demande exactement ce qu'il veut sans demander au back
4. Vous avez plusieurs frontends (web, mobile, admin)
-> un seul schéma alimente tout, pas de v1/v2
5. Vous voulez un typage fort partagé front/back
-> codegen TypeScript automatique depuis le schéma
6. Vous gérez des données très IMBRIQUÉES
-> réseau social, marketplace, analytique, CRM avancé
EXEMPLES MONDIAUX GRAPHQL :
- Shopify (e-commerce mondial)
- GitHub API v4
- Atlassian Jira / Confluence
- Hashnode (blog développeurs)
- Linear (gestion projet)
EN AFRIQUE :
- Quelques fintechs nigérianes (Flutterwave Hosted)
- Startups tech récentes Lagos / Kigali / Le Cap
Étape 6 — Installer Apollo Server (GraphQL Node.js)
mkdir api-graphql-pme && cd api-graphql-pme
npm init -y
# Installer Apollo Server v4 et Express
npm install @apollo/server graphql express cors body-parser
npm install --save-dev nodemon
# Structure
mkdir src
touch src/server.js src/schema.js src/resolvers.js
# package.json scripts
npm pkg set scripts.dev="nodemon src/server.js"
npm pkg set type="module"
// src/server.js
import express from 'express'
import {ApolloServer} from '@apollo/server'
import {expressMiddleware} from '@apollo/server/express4'
import cors from 'cors'
import bodyParser from 'body-parser'
import {typeDefs} from './schema.js'
import {resolvers} from './resolvers.js'
const app = express()
const server = new ApolloServer({typeDefs, resolvers})
await server.start()
app.use('/graphql',
cors({origin: ['https://pme-dakar.sn']}),
bodyParser.json({limit: '1mb'}),
expressMiddleware(server, {
context: async ({req}) => ({
user: req.headers.authorization
? verifyJWT(req.headers.authorization.split(' ')[1])
: null
})
})
)
app.listen(4000, () => console.log('GraphQL sur http://localhost:4000/graphql'))
Apollo Server v4 sépare le serveur GraphQL du serveur HTTP. Cela permet de l’intégrer dans Express, Fastify, Koa, ou même AWS Lambda. Le playground accessible sur /graphql en mode développement.
Étape 7 — Définir un schéma GraphQL (SDL)
// src/schema.js
export const typeDefs = `#graphql
scalar Date
type Client {
id: ID!
nom: String!
telephone: String!
email: String
ville: String!
factures(limit: Int = 10, statut: StatutFacture): [Facture!]!
stats(annee: Int!): StatsClient!
}
type Facture {
id: ID!
numero: String!
montant: Int!
statut: StatutFacture!
dateEmission: Date!
client: Client!
}
enum StatutFacture {
BROUILLON
ENVOYEE
PAYEE
ANNULEE
}
type StatsClient {
totalPaye: Int!
totalEnAttente: Int!
nbFactures: Int!
}
type Query {
client(id: ID!): Client
clients(ville: String, limit: Int = 20, offset: Int = 0): [Client!]!
facture(id: ID!): Facture
recherche(terme: String!): [Client!]!
}
input CreateClientInput {
nom: String!
telephone: String!
email: String
ville: String = "Dakar"
}
type Mutation {
creerClient(input: CreateClientInput!): Client!
modifierClient(id: ID!, input: CreateClientInput!): Client!
supprimerClient(id: ID!): Boolean!
}
type Subscription {
nouveauClient: Client!
}
`
SDL (Schema Definition Language) est le contrat entre frontend et backend. Tout est typé : fini les « le champ existe-t-il ? » en JavaScript. Les ! signifient « non null obligatoire ».
Étape 8 — Implémenter les resolvers
// src/resolvers.js
import db from './db.js'
export const resolvers = {
Query: {
client: async (_, {id}, ctx) => {
requireAuth(ctx)
const result = await db.query(
'SELECT * FROM clients WHERE id = $1', [id])
return result.rows[0] || null
},
clients: async (_, {ville, limit, offset}, ctx) => {
requireAuth(ctx)
const where = ville ? 'WHERE ville = $3' : ''
const params = [limit, offset]
if (ville) params.push(ville)
const result = await db.query(
`SELECT * FROM clients ${where} LIMIT $1 OFFSET $2`, params)
return result.rows
},
recherche: async (_, {terme}, ctx) => {
requireAuth(ctx)
const result = await db.query(
`SELECT * FROM clients WHERE nom ILIKE $1 LIMIT 50`,
[`%${terme}%`])
return result.rows
}
},
Client: {
// Resolver sur un champ : appelé seulement si demandé
factures: async (parent, {limit, statut}, ctx) => {
// ATTENTION : N+1 query si appelé pour 100 clients
// Utiliser DataLoader pour batcher (étape 9)
const where = statut ? 'AND statut = $3' : ''
const params = [parent.id, limit]
if (statut) params.push(statut.toLowerCase())
const result = await db.query(
`SELECT * FROM factures WHERE client_id = $1 ${where}
ORDER BY date_emission DESC LIMIT $2`, params)
return result.rows
},
stats: async (parent, {annee}, ctx) => {
const result = await db.query(`
SELECT
COALESCE(SUM(CASE WHEN statut='payee' THEN montant ELSE 0 END), 0) AS total_paye,
COALESCE(SUM(CASE WHEN statut='envoyee' THEN montant ELSE 0 END), 0) AS total_en_attente,
COUNT(*) AS nb_factures
FROM factures
WHERE client_id = $1 AND EXTRACT(YEAR FROM date_emission) = $2
`, [parent.id, annee])
const row = result.rows[0]
return {
totalPaye: parseInt(row.total_paye),
totalEnAttente: parseInt(row.total_en_attente),
nbFactures: parseInt(row.nb_factures)
}
}
},
Mutation: {
creerClient: async (_, {input}, ctx) => {
requireAuth(ctx, 'admin')
const r = await db.query(
`INSERT INTO clients (nom, telephone, email, ville)
VALUES ($1, $2, $3, $4) RETURNING *`,
[input.nom, input.telephone, input.email, input.ville])
return r.rows[0]
}
}
}
function requireAuth(ctx, role) {
if (!ctx.user) throw new Error('Non authentifié')
if (role && ctx.user.role !== role && ctx.user.role !== 'admin')
throw new Error('Permission refusée')
}
Les resolvers sont appelés UNIQUEMENT pour les champs demandés. Si la requête ne demande pas « stats », la requête SQL des stats n’est pas exécutée. C’est le grand avantage GraphQL.
Étape 9 — Le piège N+1 et DataLoader
PROBLÈME N+1 :
Requête GraphQL :
{
clients(limit: 100) {
nom
factures { numero }
}
}
Sans précaution :
- 1 requête pour récupérer 100 clients
- 100 requêtes pour récupérer les factures de chaque client
TOTAL : 101 requêtes SQL ! Désastreux.
SOLUTION : DataLoader (de Facebook)
import DataLoader from 'dataloader'
const facturesLoader = new DataLoader(async (clientIds) => {
const result = await db.query(
'SELECT * FROM factures WHERE client_id = ANY($1)',
[clientIds])
// Regrouper par client_id
const byClient = {}
for (const f of result.rows) {
if (!byClient[f.client_id]) byClient[f.client_id] = []
byClient[f.client_id].push(f)
}
return clientIds.map(id => byClient[id] || [])
})
Resolver :
factures: async (parent) => facturesLoader.load(parent.id)
Désormais : 2 requêtes SQL au lieu de 101.
DataLoader est obligatoire dès qu’une API GraphQL passe en production. Sans lui, vous êtes en N+1 et chaque requête peut générer des centaines de requêtes SQL invisibles.
Étape 10 — Protections obligatoires en production
npm install graphql-depth-limit graphql-query-complexity
import depthLimit from 'graphql-depth-limit'
import {createComplexityRule, simpleEstimator} from 'graphql-query-complexity'
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [
depthLimit(7), // max 7 niveaux d'imbrication
createComplexityRule({
maximumComplexity: 1000,
estimators: [simpleEstimator({defaultComplexity: 1})],
onComplete: (complexity) =>
console.log(`Query complexity: ${complexity}`)
})
]
})
// Sans ces limites, un attaquant peut envoyer :
// {
// client(id:1) {
// factures { client { factures { client { factures {...} } } } }
// }
// }
// -> explosion exponentielle, serveur KO en 1 requête !
Une API GraphQL sans depth limit + query complexity est une bombe à retardement. Un seul attaquant avec 1 requête peut faire tomber votre serveur. Ces protections sont aussi importantes que le rate limiting REST.
Étape 11 — Authentification et autorisation
// Réutiliser le JWT de l'API REST si vous avez les deux
import jwt from 'jsonwebtoken'
function verifyJWT(token) {
try {
return jwt.verify(token, process.env.JWT_SECRET)
} catch (e) {
return null
}
}
// Le contexte est passé à chaque resolver
context: async ({req}) => {
const token = req.headers.authorization?.split(' ')[1]
const user = token ? verifyJWT(token) : null
return {user, ip: req.ip}
}
// Dans le resolver
client: async (_, {id}, {user}) => {
if (!user) throw new GraphQLError('Authentification requise', {
extensions: {code: 'UNAUTHENTICATED', http: {status: 401}}
})
if (user.role !== 'admin' && user.id !== id) {
throw new GraphQLError('Permission refusée', {
extensions: {code: 'FORBIDDEN', http: {status: 403}}
})
}
// ... récupération
}
// Pour de la granularité fine, utiliser des directives
// type Client {
// email: String @auth(requires: ADMIN)
// }
Contrairement à REST où l’autorisation est par route, en GraphQL elle est par champ ou par resolver. Plus puissant mais nécessite de penser sécurité à chaque champ exposé.
Étape 12 — Cache : le grand défi GraphQL
PROBLÈME : POST /graphql avec body différent à chaque requête
-> Cloudflare ne peut pas cacher facilement
-> Apollo Cache existe côté client mais demande config
SOLUTIONS :
1. APQ (Automatic Persisted Queries)
Le client envoie un hash SHA256 de la query.
Première requête : POST avec query + hash
Suivantes : GET ?id=hash (CACHEABLE par CDN)
2. @cacheControl directives
type Client @cacheControl(maxAge: 60) {
nom: String! @cacheControl(maxAge: 3600)
}
3. Apollo Client cache (frontend)
Le navigateur garde en RAM les résultats des queries.
Idéal pour single-page app React/Vue.
4. Cache applicatif Redis
Cacher les résultats des resolvers coûteux côté serveur.
POUR PME : si cache CDN important (site grand public),
PRÉFÉRER REST. Si app autenticée (back-office, mobile),
GraphQL fonctionne très bien sans cache CDN.
REST gagne haut la main sur le cache HTTP. Une page produit cachée 10 minutes par Cloudflare décharge 99% du trafic du serveur. GraphQL nécessite plus de travail pour atteindre le même résultat.
Étape 13 — Tester avec Postman ou Apollo Sandbox
Apollo Sandbox (gratuit, dans le navigateur) :
URL : https://studio.apollographql.com/sandbox/explorer
1. Connecter votre endpoint http://localhost:4000/graphql
2. Le schéma est introspecté automatiquement
3. Autocomplete sur les champs disponibles
4. Coloration syntaxique
5. Documentation à droite générée du schéma
6. Historique des requêtes
POSTMAN (depuis 2021) :
1. New Request -> GraphQL
2. URL : http://localhost:4000/graphql
3. Onglet "GraphQL" : query + variables séparés
4. Refresh schéma : Postman lance une introspection
EXEMPLE DE TEST AUTOMATIQUE :
import {test, expect} from 'vitest'
test('client query renvoie les bonnes données', async () => {
const res = await fetch('http://localhost:4000/graphql', {
method: 'POST',
headers: {'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token},
body: JSON.stringify({
query: `{ client(id: "42") { nom factures { numero } } }`
})
})
const data = await res.json()
expect(data.errors).toBeUndefined()
expect(data.data.client.nom).toBe('Mamadou Diop')
})
Apollo Sandbox est l’équivalent de Swagger UI pour GraphQL : un développeur tiers peut tout tester sans installer un client. Vous pouvez l’auto-héberger ou utiliser la version SaaS gratuite d’Apollo.
Étape 14 — Décision finale : matrice de choix
RÉPONDEZ AUX 5 QUESTIONS :
1. Mes clients API sont-ils principalement EXTERNES (banques, partenaires) ?
OUI : +3 REST NON : +0
2. J'ai une APP MOBILE prioritaire avec usage en zone réseau faible ?
OUI : +3 GraphQL NON : +0
3. Ma page la plus chargée affiche > 5 ressources liées ?
OUI : +2 GraphQL NON : +1 REST
4. Mon équipe back/front a déjà fait du GraphQL ?
OUI : +2 GraphQL NON : +2 REST
5. Le cache CDN (page produit publique) est-il critique ?
OUI : +2 REST NON : +0
CALCUL :
Total REST : ___ / 8
Total GraphQL : ___ / 7
INTERPRÉTATION :
- REST > 5 : choisir REST
- GraphQL > 4 : choisir GraphQL
- Différence faible : choisir REST (écosystème plus mature)
EXEMPLE PME E-COMMERCE DAKAR :
1. Externes : NON (juste app mobile + dashboard) -> 0
2. Mobile : OUI -> +3 GraphQL
3. Page riche : OUI (produit + avis + stock + similaires + vendeur) -> +2 GraphQL
4. Équipe expérimentée : NON -> +2 REST
5. Cache CDN : OUI (catalogue public) -> +2 REST
TOTAL : REST 4 / GraphQL 5
-> GraphQL léger avantage MAIS équipe junior -> commencer en REST,
migrer progressivement quand l'équipe monte en compétences.
Il n’y a pas de mauvais choix si motivé. Beaucoup de PME africaines réussissent très bien en REST seul. D’autres construisent un avantage compétitif avec GraphQL pour leur app mobile.
Erreurs fréquentes
- Choisir GraphQL parce que c’est « moderne » : si vos clients sont des intégrateurs externes ou votre équipe junior, REST sera plus rapide à livrer et maintenir.
- Lancer GraphQL en production sans depth limit ni query complexity : un seul attaquant peut faire tomber votre serveur en 1 requête. Toujours configurer ces protections.
- N+1 queries : sans DataLoader, une requête GraphQL génère des centaines de requêtes SQL. C’est le bug GraphQL le plus courant.
- Mélanger REST et GraphQL sans plan clair : si vous avez les deux, documentez quel domaine va où. Sinon les développeurs ne savent plus quoi utiliser.
- Penser que GraphQL résout tout : il ajoute de la complexité (typage, resolvers, cache, sécurité). Pour un CRUD simple, REST est plus rapide à coder.
- Penser que REST est dépassé : Stripe, GitHub, Twilio, Wave : les meilleures APIs au monde restent en REST. Maturité > mode.
- Ne pas valider les inputs en GraphQL : les types GraphQL valident la structure mais pas les règles métier (prix négatif, email invalide). Validez explicitement.
Checklist de décision REST vs GraphQL
- Audit des consommateurs de l’API (interne, mobile, partenaires externes)
- Analyse des écrans : nombre moyen de ressources liées par page
- Évaluation de la maturité de l’équipe sur les deux paradigmes
- Inventaire des contraintes réseau (mobile, zones rurales, latence)
- Stratégie de cache : CDN public ou cache applicatif uniquement ?
- Si REST : conventions URL, codes HTTP, OpenAPI 3.1, JWT, rate limit
- Si GraphQL : Apollo Server, schéma SDL, DataLoader, depth/complexity limits
- Documentation : Swagger UI (REST) ou Apollo Sandbox (GraphQL)
- Tests automatiques pour les opérations critiques
- Monitoring : logs, métriques, alertes sur erreurs et lenteurs
- Stratégie d’évolution : versioning REST ou évolution de schéma GraphQL
- Plan de formation équipe sur le paradigme choisi (1 sem REST, 1 mois GraphQL)
- Revue sécurité : auth, autorisation par champ ou par route, DDoS
- Décision documentée avec les 5 critères et révisée tous les 12 mois