Comprendre GraphQL en 5 minutes
Quand GraphQL est arrivé sur les Internets, il s’est propagé comme une épidémie. GraphQL a complètement changé la façon de faire les choses et continue sa progression partout. Si t’as cinq minutes devant toi, je t’explique tout ce que tu dois savoir.
Made in Facebook
En 2012, l’adoption des téléphones mobiles atteint des chiffres monstrueux dans le monde entier. C’est tellement l’invasion que les entreprises qui n’adaptent pas leurs produits sont en danger. À ce moment-là, c’est le cas de Facebook.
Facebook fait du web. Du coup, ils ont fait leur app IOS comme un site web, en web-view. Très vite, ils se rendent compte que c’est un peu de la merde (à cette époque). Ils décident alors de la refaire entièrement en natif, pour une meilleure expérience client. Immédiatement ils se prennent un autre mur dans la face.
L’architecture existante ne fonctionne pas. Principalement parce que les endpoints de leurs api REST existantes ne permettent pas de flexibilité sur la data. Plusieurs aller-retour sur différents endpoints sont nécessaires pour de la donnée imbriquée, causant des lenteurs et des inconsistances. Une partie du payload n’est pas nécessaire pour la plupart des requêtes, causant des transferts de données inutiles. Et surtout c’est fastidieux pour Facebook de gérer autant de call HTTP.
C’est dans ce contexte infernal qu’en février 2012 Lee Byron, Dan Schafer et Nick Schrock se réservent des bureaux dans un coin de Facebook.
Très vite un premier prototype de GraphQL, appelé alors SuperGraph, est produit par nos trois devs. En août 2012, GraphQL est shippé en prod avec la nouvelle app native de Facebook. En 2015, la première version public arrive sur les internet. GraphQL est toujours présent aujourd’hui quand tu scroll ton mur Facebook. Mais comment ont-il réglé un problème qui touchait non seulement Facebook, mais aussi toute l’industrie ?
C’est quoi GraphQL ?
GraphQL est un langage de requêtes de données pour API. QL, comme dans SQL, veut dire Query Language. GraphQL permet de manipuler de la donnée de façon simple, flexible et très précise. GraphQL n’est pas un langage de programmation ou un framework. GraphQL est une spécification pour implémenter ton API. Concrètement, dans l’utilisation, ça ressemble à ça.
Requête
{ pokemons { name, abilities { name, damage, accuracy, mana, type } } }
Réponse
{ "data": { "pokemons": [ { "name": "pikachu", "abilities": [ { "name": "Thunder punch", "damage": 75, "accuracy": 70, "mana": 15, "type": "physical" }, { "name": "Thunderbolt", "damage": 90, "accuracy": 80, "mana": 15, "type": "electric" } ] }, { "name": "mewtwo", "abilities": [ { "name": "Earthquake", "damage": 130, "accuracy": 100, "mana": 20, "type": "ground" }, { "name": "Brutal swing", "damage": 180, "accuracy": 90, "mana": 25, "type": "physical" } ] } ] } }
C’est comme ça qu’on demande et qu’on reçoit de la donnée en utilisant GraphQL. OK, pour le moment, c’est pas clair. Déjà, ca rentre ou dans ton architecture ce machin ?
Le bonhomme qui sourit, c’est toi. Et toi, pour faire le payload que je t’ai montré plus haut avec les Pokemon et leur habilité tu galères. Tu galères parce que l’API REST que tu utilises n’est pas fait pour ton besoin. Tu te retrouves à faire un call par Pokemon, puis un call par habilité pour chaque Pokemon.
À chaque fois la logique de ton application fait une demande à la base de données et te renvoie un bout de payload. Et du coup, malgré ton sourire apparent, t’as envie de te tirer une balle. C’est la que GraphQL intervient.
Avec GraphQL, finie la galère. Tu fais un seul POST et tu demandes exactement ce que tu veux via une requête GraphQL. Ensuite, c’est le serveur qui gère sa merde, toi tu reçois ton payload complet.
Avec REST tu recevais des objets définis par des endpoints. Avec GraphQL, tu ne t’adaptes pas un objet défini par le backend, tu définis dynamiquement l’objet que tu vas recevoir côté client. Et ça, ça change tout.
OK alors, c’est bien beau tout ça, mais concrètement comment ça marche ? Comment GraphQL accède à ta base de données et fait des requêtes ? Pour vraiment comprendre GraphQL, il faut mettre les mains dedans.
Fais voir le code
Je vais te faire une implémentation côté Javascript (NodeJS). Mais sache que tout ce qui suit est applicable dans n’importe quels langages. La logique GraphQL reste la même partout puisqu’il s’agit avant tout d’une spécification.
Pour commencer à bosser sur du GraphQL direction le site officiel et leur liste d’implémentation dans tous les langages de la terre. Pour faire simple avec NodeJS on a besoin des modules express-graphql et graphql. Commençons par monter le serveur de base.
index.js
const path = require("path"); const express = require("express"); const graphqlHTTP = require("express-graphql"); const graphql = require("graphql"); const { query } = require(path.resolve("schema/query")); const graphQLSchema = new graphql.GraphQLSchema({ query }); const app = express(); app.use( "/graphql", graphqlHTTP({ schema: graphQLSchema, graphiql: true }) ); app.listen(8080);
Tout d’abord on appelle nos dépendances. Ensuite ligne 6 on va chercher notre requête racine qu’on passe dans le schéma principale ligne 7. On lance notre serveur express, on expose la route /graphql via un middleware express et enfin on écoute sur le port 8080. Voyons ce qui se passe à l’intérieur du schéma maintenant.
schema/query.js
const path = require("path"); const { GraphQLObjectType, GraphQLList } = require("graphql"); const { pokemonsType } = require(path.resolve("schema/types")); const RootQuery = new GraphQLObjectType({ name: "RootQueryType", type: "Query", fields: { pokemons: { type: new GraphQLList(pokemonsType), resolve() { const data = require(path.resolve("data/pokemons.json")); return data; } } } }); exports.query = RootQuery;
Le schéma est central dans GraphQL. Il va dicter la communication entre ton client et ton serveur. Il spécifie les requêtes que tes clients peuvent faire, les types de données récupérables et les relations entre ces types. Tout est défini dans ce schéma. À commencer par la requête racine.
La requête racine permet à GraphQL de savoir quel type de données est possible d’aller chercher. Et là, dans ma requête racine, je spécifie que j’ai un champ pokemons ligne 9 de type liste de type pokemon ligne 10.
Ensuite on a un résolveur ligne 11. C’est les résolveurs qui font le travail d’aller chercher ta data dans ta base de données. Un résolveur est assigné à chacun de tes champs. Et le résolveur de mon champ pokemons est une liste d’objet pokemon. Mon résolveur ici renvoie la data via un fichier JSON qui correspond à un array d’objet pokemon.
Je renvoie un JSON pour la data par souci de simplicité et concision. Mais dans la vraie vie c’est ici que t’es censé appeler ta base de données, faire des requêtes et renvoyer la data. Maintenant, voyons à quoi ressemble les types.
schema/types.js
const path = require("path"); const graphql = require("graphql"); const { GraphQLObjectType, GraphQLString, GraphQLList } = graphql; const abilitiesType = new GraphQLObjectType({ name: "ability", fields: { name: { type: GraphQLString, resolve: parent => parent.name }, damage: { type: GraphQLString, resolve: parent => parent.damage }, accuracy: { type: GraphQLString, resolve: parent => parent.accuracy }, mana: { type: GraphQLString, resolve: parent => parent.mana }, type: { type: GraphQLString, resolve: parent => parent.type } } }); const pokemonsType = new GraphQLObjectType({ name: "pokemons", fields: { name: { type: GraphQLString, resolve: parent => parent.name }, abilities: { type: new GraphQLList(abilitiesType), resolve(parent) { const abilities = require(path.resolve("data/abilities.json")); return abilities.filter(ability => ability.linkedTo.includes(parent.name) ); } } } }); exports.pokemonsType = pokemonsType;
Le principe reste le même. On crée des types d’objets GraphQL qui représentent notre structure de données. On spécifie des champs et pour chaque champ, on assigne un résolveur qui va chercher la bonne data. Il est intéressant de voir ici que j’utilise le contexte du parent pour filtrer quelles habilitées renvoyer pour chaque pokémon ligne 44.
Si tu veux voir une version fonctionnelle de cette implémentation, je t’ai fait une petite sandbox publique où tu peux jouer avec. Tu peux voir tous les fichiers, dont les fichiers JSON, et modifier ce que tu veux ! Aller, je fais le fifou, je te l’embed même juste en dessous.
Toi aussi tu pourrais faire le fifou. À la place de la donnée via fichier JSON tu pourrais implémenter la même chose en faisant des fech sur PokéAPI. Ça te permettrait de pratiquer GraphQL en plus.
Épilogue
Voilà, je ne peux pas aller plus loin dans la présentation. Je dépasse déjà les cinq minutes du temps que tu m’as accordé. Y’a beaucoup plus à dire sur cette techno. Les mutations, le cache, les variables et les contextes. Je vais me contenter de l’essentiel. Si tu veux en savoir plus et que tu as du temps devant toi, je te conseille cet article très complet !
Les inconvénients que tu cite pour une API REST tiennent pour une API type CRUD développée comme un cochon. Une API suivant la json-api par exemple est beaucoup plus flexible avec des possibilités de tri, filtrage, inclusion d’objet dépendant etc… . Après cela n’enlève rien a graphql.
Exact ! C’est pour ça que je dis que l’API n’est pas fait pour le besoin.
C’est plus pour faire comprendre plus rapidement le concept de GraphQL qu’autre chose.
L’architecture de node est particulièrement compliquée pour faire des json-api. En tout cas c’est mon avis. Toujours établir les relations dans les requêtes Sequelize (ce qu’on utilisait au travail pour ça), c’était vraiment fastidieux. C’est aussi pour cette raison que j’ai migré sur Laravel pour ça. Tout se fait simplement avec des ressources qu’on renvoie, il va chercher tout ça tout seul comme un grand avec le modèle.
Mais GraphQL est juste génial, j’ai eu l’occasion de jouer avec sur un side project. ❤️
Merci pour tout article 🙂
Concernant la securité, ça donne quoi ? J’ai l’impression qu’une fois que tu as accès au schema graphql, tu peux tout faire…
Un début de réponse (?) : https://leapgraph.com/graphql-api-security
Merci
Apollon Shield pour limiter les accès aux resolvers.
JWT pour renvoyer les data te concernant.
Il est le rapport avec la photo du toutou là-haut ? 😇
Du coup cela ne remplace quand même pas la REST API car ces n’est que de la lecture, non?
Il faut quoi qu’il arrive coder ce qu’il faut pour insérer les objets.
Non, c’est ici que les mutations interviennent, non présentées dans l’article, mais évoquées à la fin 🙂
bonjour, en tant qu’ancien dev (oui très ancien), je regarde avec grand intérêt toutes ces nouvelles technos, tout ça me semble parfois assez abscons, n’étant pas, plus, dans les contextes, mais j’essaie de les comprendre, et suis souvent interloqué par un réflexion que je n’arrive pas à m’enlever de la tête « pourquoi tout ca ? » (encore une fois j’ai bien conscience de ne plus être dans le « mov », mais quand même…)
il doit me manquer des « billes », c’est clair, mais si je vulgarise au max mes questionnements sur les raisons du « tout ça, pour ca »: le but reste dans tout les cas l’affichage « local » de données via des requêtes sur une ressource distante (base du mode client serveur). ok.
Je vais faire certainement hurler, mais c’est volontaire, quelle est au final, la différence avec par exemple, une simple requête ajax ?
Si je reprend le cas de départ, les xxx requêtes lié a la réception pokemon, je comprend sans soucis l’aberration de multiples aller/retour d’infos, et qu’une seule suffit, mais ca me « semble » plus lié a un soucis d’analyse du besoin que lié a une techno (et c’est la que vous me perdez :D).
J’ai l’impression (et je suis persuadé a tord) qu’il faut maintenant sans cesse de nouvelles technos qui compensent une nouvelles technos qui elle mème compensait une nouvelle techno et ainsi de suite…
comme une « évidence » je dirais que de tout temps, il a été (et heureusement) possible de recuperer en un bloc les datas nécessaire sans devoir faire 25 aller/retour… et en écrivant cela je repense a un article lu sur ce site (que je valide a 110%) sur l’approche complexification de certains développement.
Sans devoir me faire rattraper les xx années que j’ai accumulé depuis le temps ou je codais, quelqu’un pourra t’ il, sans besoin de détails, de m’expliquer pourquoi, ces nouvelles techno (que je m’efforce de comprendre, voir même de tester) me laisse toujours le même sentiment: pourquoi faire simple quand ont peut faire compliqué ? je sais qu’il y a une raison 😉 mais je ne cesse de la chercher !
Merci les jeunes (et merci pour ses articles « 5mn »)
Hups, Désolé pour le pavé
Bonjour,
En tant qu’ancien dév’ qui fait toujours du dév « à l’ancienne », c’est à dire sans ce préoccuper de toutes ces tentatives de normalisation du code, et qui malgré le décalage, se retrouve néanmoins à piloter des projets avec des technos du jour, j’ai une amorce de réponse à la question de AD.
La tendance de l’époque est au développement « one-shot », rapide et efficace autant que possible, sans prise en compte de la stabilité dans le temps : la tenue de l’appli dans 5 ans ? pas le temps d’y réfléchir… Donc pour aller vite, et pas trop mal, on fait appel à des technos avec le maximum de formalismes et d’automatismes, des trucs de plus en plus bordés pour ne pas avoir à réfléchir, et se contenter d’implémenter par assemblage. Assembler des briques et ne pas avoir à coder.
Evidemment, ces automatismes ont leur limites conceptuelles et pratiques, et chaque insuffisance donne l’envie d’en ajouter une couche, comme GraphQL qui veut compenser les faiblesses d’une certaine normalisation des APIs REST et de leurs routes préfabriquées. Oui, on peut ignorer GraphQL quand on peut faire une simple requête HTTP vers une API REST qui retourne tout ce qu’on veut, en un seul paquet si nécessaire, ou en plusieurs paquets si on préfère, vu qu’il suffit de mettre au point plusieurs endpoints adaptés aux différents besoins.
Mais voilà, les devs modernes n’aiment pas qu’on code en free-style (beurk du code non normalisé), et se réfèrent pour la plupart aux technos sorties des labos GAFAM au prétexte que si EUX ont pondu ces technos, c’est qu’elles sont absolument nécessaires dans tout projet sérieux (suis pas un amateur moi môssieur), même si les contraintes desdits projets sont ridiculement plus simples que celles des applis desdits GAFAM…
Oui, j’en ai vu des devs partis sur du nodejs+nestjs+orm[prisma]+graphQL+nettoyeurs[js/css]+devpack+ci/cd pour livrer des backoffices de 3 pages. Normal, ils ne connaissent que ça, et n’ont jamais vu à quel point une petite appli « cousue main » pour ces 3 mêmes pages peut être simple, rapide à écrire et réactive à l’exécution, codée en free-style « à l’ancienne ».
Et 3 ans plus tard, quand il faut faire évoluer cette super vieille appli écrite avec ces anciennes technos dépassées d’il y a 3 ans, que plus personne ne code comme ça de nos jours môssieur, ils réécrivent tout, ou cherchent des convertisseurs automatiques vers les nouvelles technos 10.0, et la livraison est catastrophique parce qu’il faut tout retester de A-Z.
Et ce qui est en cause, ce ne sont pas les technos modernes qui sont tout autant valables et fiables que les anciennes. Ce que je critique ici, c’est de croire qu’on peut livrer des applis bien pensées en utilisant des processus semi-automatiques remplis de paramètres de configuration, quasiment dépouillés de code, sans se préoccuper de la pérennité des technos retenues.
C’est l’époque du dév’ jetable.
Oula ça sent le rance et le boomer réac « de mon temps c’était mieux », « les jeunes ils ne savent pas coder », « moi je code en freestyle, c’est plus efficace »… Oui oui, mais non en fait. Les technos ne sortent pas de nul-part, elles ont un intérêt et des usages précis.
Pour un back-office de 3 pages, on peut préférer des outils plus simples si on veut, mais des façons générales le web s’est énormément complexifié pour répondre aux nouveaux besoins, et les technos ont suivi le mouvement.
Il y a un travail énorme de normalisation/standardisation qui est nécessaire. C’est plus fastidieux, mais c’est justement ça qui apporte de la robustesse avec le temps, et qui assure qu’un nouveau dev peut monter en compétences rapidement sur un projet.
Le de freestyle à l’ancienne, c’est une pratique valable pour des « toy-projet », a alors une bonne recette pour créer de la dette technique en entreprise.
Le code s’écrit avant tout pour être lu par des humains, donc plus de formalisme, de normalisation/standardisation, c’est un pas dans la bonne direction. Étonnant qu’un « vieux » ne comprenne pas ça et vienne étaler sa frustration et sa suffisance envers des technos qu’ils ne comprend pas.
Il s’agirait de se mettre à jour, les jeunes, ils ne vous attendent pas, le monde non plus d’ailleurs.
Ah, tu sonnes comme mes stagiaires qui me disent qu’ils vont me sortir un truc plus simple avec cette nouvelle techno en 2 jours, et qui n’y arrivent pas en fin de la semaine.
les objets imbriqués json, ex la modélisation NoSQL permettent très facilement d’avoir des fields de manière dynamique.