ITSkillsCenter
Blog

Le contexte 2026 en une page

15 min de lecture

Ce que vous saurez faire à la fin

  1. Comprendre les différences fondamentales entre REST et GraphQL : architecture, requêtes, types, performance et complexité.
  2. Identifier en 5 questions si votre projet PME doit choisir REST, GraphQL, ou les deux en parallèle.
  3. Créer un serveur GraphQL minimal en Node.js avec Apollo Server et le tester avec GraphQL Playground.
  4. Mettre en place les protections obligatoires d’une API GraphQL en production : query complexity, depth limit, authentification.
  5. 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
Besoin d'un site web ?

Confiez-nous la Création de Votre Site Web

Site vitrine, e-commerce ou application web — nous transformons votre vision en réalité digitale. Accompagnement personnalisé de A à Z.

À partir de 250.000 FCFA
Parlons de Votre Projet
Publicité