Accueil / Articles

REST contre GraphQL : les véritables compromis derrière chaque architecture

Il explique les problèmes spécifiques que résolvent respectivement REST et GraphQL, leurs mécanismes internes, ainsi que les compromis cachés à prendre en compte avant de choisir l’un d’eux pour votre API.

Éditeur : REACTAPP.TOP10 min de lecture2224 mots

Sources: REST vs GraphQL: Which Problem Does Each One Actually Solve?

Lorsque l’on s’attelle à concevoir la manière dont un client et un serveur doivent communiquer, une question se pose presque à chaque fois : REST ou GraphQL ?

Les débats sur ce sujet ont tendance à le présenter comme une compétition, comme si l’une des deux solutions devait nécessairement l’emporter. Certains développeurs affirment que REST a atteint ses limites et devrait être abandonné. D’autres insistent sur le fait que GraphQL introduit des niveaux de complexité inutiles pour la plupart des projets. Aucune de ces approches n’est particulièrement utile.

Les technologies ne sont pas conçues pour gagner des points dans un débat. Elles existent pour résoudre des problèmes concrets auxquels les développeurs sont confrontés dans des circonstances spécifiques.

Adopter GraphQL simplement parce qu’il est considéré comme à la mode peut vous exposer à des problèmes de mise en cache et à des requêtes coûteuses du côté serveur que vous n’aviez pas prévues. Inversement, imposer une structure REST rigide à une application mobile gourmande en données peut ralentir l’affichage des écrans en raison d’une série de requêtes réseau successives.

Pour prendre une décision éclairée, il est utile de mettre de côté le bruit médiatique et d’examiner les motivations sous-jacentes : ce que chaque approche a été conçue pour résoudre, son fonctionnement interne, ainsi que les compromis qu’elle implique.

Quel problème REST a-t-il résolu ?

Au moment où REST est devenu la norme, les services web dépendaient fortement de protocoles tels que SOAP et XML-RPC. Ces derniers exigeaient des schémas XML rigides, des envois de messages longs et complexes, ainsi qu’une configuration minutieuse. Les clients devaient connaître les noms précis des procédures à distance simplement pour récupérer des données.

Roy Fielding a introduit REST (Representational State Transfer) en 2000.

À l’essentiel, REST proposait un concept simple mais efficace : s’appuyer sur l’architecture même du web.

Au lieu de considérer un serveur comme un moteur d’exécution de fonctions opaque, REST modélise un système comme une collection de ressources, chacune accessible via une URL claire. Les actions sur ces ressources s’expriment à l’aide de verbes HTTP standard (GET, POST, PUT, DELETE), tandis que le résultat de chaque requête est indiqué par des codes d’état HTTP familiers tels que 200 OK pour une réussite, 404 Not Found en l’absence d’une ressource, ou 500 Server Error en cas de problème côté serveur.

Resource-Oriented Architecture (REST)
Client ─── GET /api/users/12 ──────────> Server (Find user #12)
Client <── 200 OK [ { user data } ] ──── Server
Client ─── DELETE /api/posts/99 ───────> Server (Delete post #99)
Client <── 204 No Content ───────────── Server

Pourquoi REST est important

  • Simplicité et prévisibilité : Une fois que l’on comprend que /api/products fait référence à une collection de produits et que /api/products/42 pointe vers un élément spécifique, l’interface devient évidente.
  • Cachage HTTP intégré : Étant donné que les requêtes GET sont idempotentes et mappées sur des URLs uniques, des infrastructures telles que les navigateurs, les CDN (comme Cloudflare) et les proxies inversés (comme Nginx) peuvent stocker en cache les réponses directement, sans nécessiter de logique d’application supplémentaire.
  • Cycles de vie découplés : Les équipes backend peuvent restructurer librement les schémas de base de données internes, tant que le contrat HTTP public reste cohérent.
  • Celui-ci a bien servi les développeurs pendant longtemps. Mais à mesure que les applications clientes devenaient plus sophistiquées, certains problèmes ont commencé à apparaître.

    Nouveaux problèmes : récupération excessive et insuffisante de données

    Imaginez une application mobile qui affiche l’écran du profil d’un utilisateur. Cet écran a besoin précisément de trois éléments :

    • Le nom de l’utilisateur
    • Une URL menant à l’avatar de l’utilisateur
    • Les titres des trois derniers articles du blog de l’utilisateur

    Avec une API REST conventionnelle, répondre à cette exigence modeste provoque souvent deux problèmes distincts.

    1. Récupération excessive de données

    GET /api/users/12.

    {
      "id": 12,
      "name": "Sarah Connor",
      "avatarUrl": "https://cdn.example.com/sarah.jpg",
      "email": "sarah@example.com",
      "address": { "street": "...", "zip": "...", "country": "..." },
      "billingHistory": [ ... ],
      "preferences": { ... },
      "createdAt": "2021-04-12T08:14:00Z"
    }
    

    L’écran n’avait besoin que de name et avatarUrl, or il a fini par récupérer des informations d’adresse non pertinentes, des données de facturation et des paramètres de préférences. Sur une connexion cellulaire lente, le transfert de bytes inutiles consomme de la batterie et ralentit l’affichage. Ce phénomène est connu sous le nom de récupération excessive de données.

    2. Récupération insuffisante de données (le problème réseau N+1)

    /api/users/12 ne les contenait pas.

    Ainsi, une deuxième requête est envoyée : GET /api/users/12/posts.

    Supposons que cet endpoint ne renvoie qu’une simple liste d’identifiants de publications. Dans ce cas, le client devra alors effectuer trois requêtes distinctes : une pour récupérer /api/posts/101, une deuxième pour obtenir /api/posts/102, et une troisième pour récupérer /api/posts/103.

    À ce stade, le dispositif a effectué plusieurs allers-retours réseau successifs rien que pour afficher une seule page. Ce schéma est appelé under-fetching.

    Quel problème GraphQL a-t-il réellement résolu ?

    Facebook est tombé précisément sur ce goulot d’étranglement vers 2012, alors qu’il passait son application phare des versions basées sur le web mobile à des applications natives. Les connexions mobiles étaient peu fiables et lentes, et l’interface de Facebook était constituée de données étroitement interconnectées — utilisateurs, amis, commentaires, likes, réactions.

    La conception d’un point de terminaison REST dédié à chaque écran, du type /api/mobile-profile-screen, est rapidement devenue un modèle difficile à gérer.

    Pour résoudre ce problème, Facebook a développé GraphQL.

    GraphQL n’est pas une technologie de base de données. C’est un langage de requête pour les APIs, associé à un environnement d’exécution côté serveur qui traite les requêtes en fonction d’un système de types que vous définez.

    Au lieu d’effectuer plusieurs allers-retours vers différents points de terminaison, le client envoie une seule requête POST contenant une requête structurée qui précise exactement la forme des données nécessaires. Le serveur renvoie alors un en-tête JSON correspondant parfaitement à cette forme.

    L’approche GraphQL

    Voici comment ce même écran de profil demanderait ses données à l’aide de GraphQL :

    GraphQL

    query GetUserProfile {
      user(id: "12") {
        name
        avatarUrl
        posts(limit: 3) {
          title
        }
      }
    }
    

    Et le serveur renvoie exactement ce qui a été demandé, rien de plus :

    JSON

    {
      "data": {
        "user": {
          "name": "Sarah Connor",
          "avatarUrl": "https://cdn.example.com/sarah.jpg",
          "posts": [
            { "title": "Building Reliable Systems" },
            { "title": "Understanding Memory Leaks" },
            { "title": "A Guide to Clean Architecture" }
          ]
        }
      }
    }
    

    Aucune bande passante n’est gaspillée à cause de champs inutilisés, et il n’y a pas besoin de deux ou trois requêtes pour récupérer des données associées. Une seule appel réseau fournit précisément les données nécessaires à l’affichage.

    Décortiquer le fonctionnement

    Pour bien comprendre les compromis impliqués, il est utile d’examiner comment chaque approche gère l’exécution du côté serveur.

    Le modèle basé sur des points de terminaison de REST

    Dans un framework comme Express.js, les routes associent directement les URLs reçues aux fonctions de traitement :

    JavaScript

    // Express.js REST route
    app.get('/api/users/:id', async (req, res) => {
      const user = await db.users.findById(req.params.id);
      if (!user) return res.status(404).json({ error: 'User not found' });
      res.json(user);
    });
    

    Le développeur backend a un contrôle total sur la logique de requête, les appels à la base de données et la structure de la réponse. L’optimisation des performances est simple dans ce cadre : il suffit d’accéder directement au gestionnaire du point de terminaison concerné pour optimiser l’instruction SQL ou y ajouter un mécanisme de mise en cache.

    Le modèle basé sur des schémas et des résolveurs de GraphQL

    GraphQL est basé sur un schéma, ainsi que sur des fonctions individuelles appelées resolveurs:

    JavaScript

    // Type definitions
    const typeDefs = `
      type Post {
        id: ID!
        title: String!
      }
    
      type User {
        id: ID!
        name: String!
        avatarUrl: String!
        posts: [Post]
      }
    
      type Query {
        user(id: ID!): User
      }
    `;
    
    // Resolvers map fields to actual data sources
    const resolvers = {
      Query: {
        user: async (_, { id }) => {
          return await db.users.findById(id);
        },
      },
      User: {
        posts: async (parentUser) => {
          // Runs when the client query includes the 'posts' field
          return await db.posts.findByAuthorId(parentUser.id);
        },
      },
    };
    

    Lorsqu’une requête arrive, le moteur GraphQL la compare au schéma, puis parcourt les resolveurs en les exécutant selon un modèle en arbre.

    Cela offre aux équipes frontend une grande liberté pour structurer leurs requêtes. Cependant, il y a un point important à noter : c’est le client qui détermine la forme de la requête, mais c’est le serveur qui doit assumer le coût de sa mise en œuvre.

    Quel est le coût caché de GraphQL ?

    Ce niveau de flexibilité n’est pas gratuit. Passer de REST à GraphQL déplace en fait la complexité du client vers le serveur.

    Le problème des requêtes N+1 au niveau de la base de données

    Examinons à nouveau la configuration du résolveur ci-dessus. Que se passe-t-il si un client demande une liste de 20 utilisateurs ainsi que les publications de chacun d’eux ?

    GraphQL

    query {
      users {
        name
        posts {
          title
        }
      }
    }
    
    • Le résolveur users s’exécute une seule fois, en récupérant les 20 lignes d’un seul coup (SELECT * FROM users).
    • Le résolveur posts est ensuite déclenché séparément pour chaque utilisateur individuel retourné.
    • Résultat final : votre base de données doit gérer 1 requête pour la liste des utilisateurs plus 20 requêtes supplémentaires pour les publications — soit au total 21 requêtes.

    Avec REST, on regroupe généralement cela en une seule jointure SQL dans le code de l’endpoint (SELECT * FROM users JOIN posts...). Avec GraphQL, il faut des outils de batchage comme DataLoader pour rassembler ces requêtes dispersées et les fusionner avant qu’elles n’atteignent la base de données.

    Les problèmes liés au cache HTTP

    Considérez une requête REST comme suit :

    GET /api/articles/42
    

    Un CDN peut facilement mettre en cache cette réponse pendant, disons, 10 minutes. Si 5 000 utilisateurs accèdent à /api/articles/42 pendant cette période, votre serveur d’origine n’a besoin de traiter que le premier — le CDN gère le reste.

    GraphQL brise ce schéma car presque toutes les opérations sont envoyées sous forme de POST vers une seule et même adresse, /graphql.

    Puisque la requête réelle se trouve dans le corps de la demande et non dans l’URL, les caches HTTP ordinaires, les proxies et les CDNs ne peuvent pas reconnaître que deux requêtes récupèrent des données identiques. Pour contourner ce problème, il faut soit un cache normalisé sophistiqué du côté client (comme Apollo Client ou Relay), soit une passerelle de mise en cache conçue spécifiquement à cet effet.

    La profondeur de la requête en tant que vecteur d’attaque

    Avec REST, il n’existe pas de moyen simple pour qu’un attaquant force un seul point d’entrée à parcourir des données relationnelles de profondeur arbitraire.

    Cependant, avec GraphQL, un acteur malveillant pourrait soumettre quelque chose comme ceci :

    GraphQL

    query EvilQuery {
      user {
        posts {
          author {
            posts {
              author {
                posts {
                  title
                }
              }
            }
          }
        }
      }
    }
    

    Sans protections telles qu’une évaluation de la complexité des requêtes ou une limitation de profondeur, une telle requête peut ralentir considérablement votre base de données ou épuiser la mémoire disponible du serveur.

    REST et GraphQL côte à côte

    FeatureRESTGraphQLRécupération de donnéesRestitue une structure fixe de données pour chaque URLRestitue exactement les champs demandés par le clientAppels réseauSouvent nécessite plusieurs allers-retoursGénéralement résolu en une seule requêteCache HTTPIntégré, simple et efficaceDifficile à mettre en œuvre ; nécessite des outils supplémentaires du côté du client ou du gatewayCurve d’apprentissageFaible, repose sur des conventions HTTP familièresPlus raide, exige une compréhension des schémas et des résolveursRapport d’erreursCodes de statut HTTP conventionnels (404, 500, 401)Presque toujours 200 OK, avec les problèmes listés dans un champ errorsUpload de fichiersSimple via des données de formulaire multipartNécessite une logique de mutation personnalisée ou des URLs d’upload pré-signéesUtilisation idéaleServices de type CRUD, microservices, API web ouvertesUI et applications riches fonctionnant sur plusieurs plateformes

    Faire l’appel : comment décider

    Lorsque REST est le choix le plus adapté

    • Votre modèle de ressources est simple. Pour des produits typiques basés sur CRUD tels que des panneaux d’administration, des tableaux de bord ou des boutiques en ligne basiques, REST vous permet de livrer plus rapidement.
    • Le cache est très important pour vous. Pour du contenu public à fort volume de lecture comme des blogs, des pages d’actualités ou des catalogues, le cache HTTP natif via les CDN est difficile à surpasser en termes de vitesse et de coût.
    • Vous publiez une API destinée à des utilisateurs externes. Les développeurs extérieurs peuvent déjà accéder aux points de terminaison REST avec curl ou fetch sans avoir besoin d’apprendre une bibliothèque client dédiée.

    Lorsque GraphQL est plus judicieux

    • Vous prenez en charge de nombreux types de clients différents. Pensez à un produit disposant de clients iOS, Android, smartwatch et web, chacun ayant besoin d’un sous-ensemble différent des mêmes données de base.
  • Votre domaine constitue un graphe étroitement interconnecté. Cela s’applique lorsque des entités telles que les utilisateurs, les groupes, les commentaires, les permissions et les médias se font constamment référence mutuellement.
  • L’interface utilisateur et le backend fonctionnent selon des calendriers distincts. Dans les équipes plus grandes, GraphQL agit comme une couche tampon : une fois que les résolveurs et les types sont définis, les ingénieurs frontend peuvent mettre en place de nouvelles interfaces sans attendre que les équipes backend ajoutent de nouveaux points d’entrée à chaque cycle.
  • Conclusion

    Aucune de ces technologies n’a été rendue obsolète par l’autre.

    • REST a répondu au besoin d’une méthode cohérente, scalable et adaptée au cache pour exposer des ressources sur le web.
    • GraphQL a répondu au besoin pour les clients de structurer eux-mêmes leurs demandes de données, réduisant ainsi les allers-retours inutiles sur des connexions mobiles lentes ou peu fiables.

    Choisir entre eux ne revient pas à suivre les tendances, mais plutôt à comprendre les véritables goulots d’étranglement auxquels votre système est confronté. Si vos plus grands défis sont les performances sur mobile et une liste toujours croissante d’extrémités spécifiques au frontend, GraphQL peut constituer une solution efficace. Si ce que vous valorisez le plus, c’est la simplicité, un cache fiable et un backend facile à entretenir, REST reste une option solide et éprouvée.