đ GraphQL â Le Guide Ultime
Deep Dive (Détaillé) : Schéma (SDL), Queries, Mutations, Résolveurs, Fédération & Liens Externes.
1. C'est quoi GraphQL ?
Un langage de requĂȘte pour API. Créé par Facebook. La solution au "Over/Under-fetching".
API Netflix Shopify2. GraphQL vs REST
Le changement de paradigme. Multi-endpoints (REST) vs Single-endpoint (GraphQL). RequĂȘte client.
REST Single Endpoint3. đ Le SchĂ©ma (SDL)
Le "Contrat". Le cĆur de GraphQL. type, scalar, !, interface, union.
4. Queries (Lecture)
Demander des données. Nids (Nesting). __typename, Directives (@include, @skip).
5. Mutations (Ăcriture)
Changer les données (create, update, delete). input types. Renvoi de l'état.
6. Subscriptions (Temps-réel)
Le "Push" de données. Géré via WebSockets. EcosystÚme PubSub (Apollo).
Real-time WebSocket7. Résolveurs (Backend)
Le "Comment". Les arguments (parent, args, context). Le "N+1 Problem" & DataLoader.
8. Types (Scalaires & Objets)
String, ID!. Listes []. Enum. Le type spécial Input.
9. Le "Supergraph" (Fédération)
Le "GraphQL des Microservices". Unir 20 APIs GraphQL en une seule. Apollo Federation.
Federation Microservices10. Variables & Fragments
RequĂȘtes dynamiques ($variable). RĂ©utiliser des champs (...fragment). SĂ©curitĂ©.
11. ĂcosystĂšme & Liens
Apollo, Relay, GraphiQL. Introspection. Liens vers tutos et docs officielles.
Apollo GraphiQL Links12. Cheat-Sheet
SDL (type, input, interface), Query ($, @include, ...on).
GraphQL est un langage de requĂȘte (Query Language) pour vos APIs, et un runtime cĂŽtĂ© serveur pour exĂ©cuter ces requĂȘtes. Il a Ă©tĂ© créé par Facebook en 2012 (rendu public en 2015) pour rĂ©soudre les problĂšmes de leurs applications mobiles.
L'idée centrale est de donner le pouvoir au client (l'app mobile, le site web) de demander *exactement* les données dont il a besoin, ni plus, ni moins.
Le ProblĂšme avec les APIs "REST" Classiques
Imaginons un blog. Pour afficher la page d'un article, une app mobile (le "client") a besoin :
1. Des infos sur l'article (titre, contenu).
2. Des infos sur l'auteur (nom, avatar).
3. La liste des commentaires.
Avec une API REST classique, le client doit faire 3 appels réseau (voire plus) :
GET /api/articles/123 --> (Renvoie le titre, contenu, et "auteur_id") GET /api/users/456 --> (On utilise "auteur_id" pour chercher l'auteur) GET /api/articles/123/comments --> (On cherche les commentaires)
Le problĂšme du "Over-fetching" et "Under-fetching"
| ProblĂšme | Description |
|---|---|
| Over-fetching (Trop de données) | Le GET /api/users/456 renvoie l'objet user *entier* (nom, avatar, email, date_creation, adresse, etc.) alors que l'app n'avait besoin que du nom et de l'avatar. On gaspille de la bande passante. |
| Under-fetching (Pas assez de données) | Le GET /api/articles/123 ne renvoie pas l'auteur (juste son ID) et pas les commentaires. L'app doit faire *d'autres* appels pour avoir tout ce qu'il lui faut (c'est le "N+1 query problem" cÎté client). |
La Solution GraphQL : 1 RequĂȘte, 1 RĂ©ponse
Avec GraphQL, le client fait 1 seul appel (un POST sur /graphql) avec une "requĂȘte" qui dĂ©crit *exactement* la forme des donnĂ©es voulues :
# Le client envoie CETTE requĂȘte (qui ressemble Ă du JSON)
query {
article(id: "123") {
titre
contenu
auteur {
nom
avatar
}
commentaires {
texte
}
}
}
Le serveur GraphQL (intelligent) comprend cette requĂȘte, va chercher les donnĂ©es (en 1 ou 3 fois, c'est son problĂšme) et renvoie un JSON unique qui *correspond parfaitement* Ă la forme de la requĂȘte.
Qui utilise GraphQL ? (Exemples de Projets)
- Netflix : L'utilise pour son API de "gateway" qui unifie des centaines de microservices backend pour servir les apps TV, web, et mobiles.
- GitHub : Leur API publique (v4) est 100% GraphQL. Elle a remplacé leur API REST (v3).
- Shopify : Utilise GraphQL massivement pour les APIs de sa plateforme e-commerce, permettant aux développeurs de plugins de ne récupérer que les infos produits/clients nécessaires.
- Airbnb, Twitter, PayPal... : De nombreuses entreprises l'utilisent pour unifier leurs sources de données.
GraphQL n'est pas "mieux" que REST dans l'absolu, mais il répond à des problÚmes différents, notamment pour les applications complexes et les clients multiples (web, mobile, montre connectée).
Tableau Comparatif Fondamental
| Concept | API REST (Classique) | API GraphQL |
|---|---|---|
| Approche | Centrée sur les Ressources (ex: /users, /articles). | Centrée sur le Client (le client *décrit* ce qu'il veut). |
| Endpoint (Point d'accĂšs) | Multiple Endpoints (ex: /users, /articles/123). | Un Seul Endpoint (typiquement /graphql). |
| Verbes HTTP | Utilise les verbes (GET, POST, PUT, DELETE). | Utilise presque exclusivement POST (pour tout). |
| Forme de la rĂ©ponse | DĂ©fini par le Serveur. (GET /users renvoie *toujours* la mĂȘme structure). | DĂ©fini par le Client. (La requĂȘte dicte la forme du JSON de retour). |
| ProblÚme | Over-fetching / Under-fetching (cÎté client). | Complexité du "N+1" cÎté serveur (à gérer avec les résolveurs). |
| Contrat (Schema) | Optionnel (ex: OpenAPI/Swagger). | Obligatoire et central. Le SchĂ©ma (SDL) est le cĆur. |
| Gestion des versions | Souvent via URL (/v1/, /v2/). Casser l'API est facile. | "Sans version" (Versionless). On ajoute des champs sans casser les clients. On "déprécie" (@deprecated) les anciens. |
Exemple de Scénario : Le "Dashboard"
Un dashboard doit afficher : l'utilisateur courant, ses 5 derniers articles, et ses 3 derniers commentaires.
Approche REST
Le client doit connaĂźtre et appeler 3 endpoints diffĂ©rents, puis assembler les donnĂ©es lui-mĂȘme.
// Appel 1 GET /api/me // Appel 2 GET /api/me/articles?limit=5 // Appel 3 GET /api/me/comments?limit=3
Si demain le dashboard a besoin des "likes" des articles, le backend doit modifier l'endpoint /api/me/articles (risque de casser l'ancienne app) ou l'app doit faire 5 appels de plus (GET /api/articles/X/likes).
Approche GraphQL
Le client envoie une seule requĂȘte au /graphql.
// Appel 1 (POST /graphql)
query {
me {
nom
email
derniersArticles(limit: 5) {
titre
datePublication
}
derniersCommentaires(limit: 3) {
texte
article { titre }
}
}
}
Si demain le dashboard a besoin des "likes", le client (le front-end) modifie juste sa requĂȘte (si le champ `likes` existe dans le schĂ©ma) sans aucun changement backend.
Le SchĂ©ma est le cĆur absolu de GraphQL. C'est le contrat formel entre le client et le serveur. Il dĂ©finit 100% de ce qu'un client *peut* demander.
Il est écrit en SDL (Schema Definition Language). C'est un langage simple et lisible.
Types d'Entrée : Query, Mutation, Subscription
Un schéma expose 3 "types" principaux qui définissent les points d'entrée de l'API :
type Query { ... }: DĂ©finit toutes les opĂ©rations de Lecture possibles (ex:user(id: ID!): User,allPosts: [Post]).type Mutation { ... }: DĂ©finit toutes les opĂ©rations d'Ăcriture possibles (ex:createPost(title: String!): Post).type Subscription { ... }: DĂ©finit toutes les opĂ©rations Temps-RĂ©el possibles (ex:newCommentOnPost(postId: ID!): Comment).
Exemple de Schéma (schema.graphql) - Version Enrichie
# 1. Définition d'un type "scalaire" personnalisé (le format de base)
scalar Date
# 2. Définition d'un "Enum" (choix limité)
enum Role {
USER
ADMIN
}
# 3. Définition d'une "Interface" (Polymorphisme)
# Un contrat que d'autres types doivent implémenter.
interface Node {
id: ID!
createdAt: Date!
}
# 4. Définition d'un type Objet "User" (implémente Node)
type User implements Node {
id: ID! # ! = Non-Null
createdAt: Date!
username: String!
email: String!
role: Role!
posts: [Post] # Un utilisateur peut avoir une LISTE de Posts
}
# 5. Définition d'un type Objet "Post" (implémente Node)
type Post implements Node {
id: ID!
createdAt: Date!
title: String!
content: String
author: User! # Un Post a UN Auteur (lien vers le type User)
comments: [Comment] # Un Post a une LISTE de Comments
}
# 6. Définition d'une "Union" (Polymorphisme)
# Un "SearchResult" peut ĂȘtre SOIT un User, SOIT un Post.
union SearchResult = User | Post
# 7. Point d'entrée pour la LECTURE
type Query {
user(id: ID!): User
post(id: ID!): Post
allPosts: [Post!]!
# Exemple d'union
search(term: String!): [SearchResult]
}
# 8. Point d'entrée pour l'ECRITURE
type Mutation {
createPost(title: String!, content: String): Post
addComment(postId: ID!, text: String!): Comment
}
Une Query est l'opération de base pour lire des données. Le client "dessine" la forme du JSON qu'il souhaite recevoir, en respectant le Schéma.
Exemple 1 : RequĂȘte ImbriquĂ©e (Le "Graph")
Je veux le titre du post "123", mais aussi le username de son auteur et le text de tous ses commentaires.
# REQUĂTE CLIENT
query {
post(id: "123") {
title
author {
username
}
comments {
text
}
}
}
# RĂPONSE SERVEUR (JSON)
{
"data": {
"post": {
"title": "Mon Premier Article",
"author": { "username": "JeanDupont" },
"comments": [
{ "text": "Super article !" },
{ "text": "Je ne suis pas d'accord." }
]
}
}
}
Exemple 2 : Directives (@include / @skip)
Les directives sont de la logique conditionnelle *cĂŽtĂ© client*. Elles permettent d'altĂ©rer la structure de la requĂȘte sans changer le string.
# REQUĂTE CLIENT (avec variable $withComments = false)
query GetPost($postId: ID!, $withComments: Boolean!) {
post(id: $postId) {
title
# Le champ 'comments' ne sera inclus que si $withComments est true
comments @include(if: $withComments) {
text
}
# Le champ 'author' sera sauté si $withComments est true
author @skip(if: $withComments) {
username
}
}
}
# RĂPONSE SERVEUR (si $withComments = false)
{
"data": {
"post": {
"title": "Mon Premier Article",
"author": { "username": "JeanDupont" }
# Le champ 'comments' n'est pas présent !
}
}
}
Exemple 3 : Introspection (__typename) et Unions
Quand une requĂȘte renvoie une Union (ex: SearchResult), comment savoir si c'est un User ou un Post ? On utilise __typename et des fragments "inline" (... on Type).
# REQUĂTE CLIENT
query {
search(term: "GraphQL") {
__typename # Champ magique : renvoie le nom du type
... on User {
# Champs Ă prendre SI c'est un User
username
}
... on Post {
# Champs Ă prendre SI c'est un Post
title
author { username }
}
}
}
# RĂPONSE SERVEUR
{
"data": {
"search": [
{ "__typename": "Post", "title": "GraphQL est super" , "author": { "username": "Jean" } },
{ "__typename": "User", "username": "GraphQLFan" }
]
}
}
Une Mutation est l'opĂ©ration pour modifier les donnĂ©es. La convention est que la mutation renvoie l'objet qui vient d'ĂȘtre modifiĂ©. Cela permet au client de rĂ©cupĂ©rer la nouvelle donnĂ©e (ex: l'ID gĂ©nĂ©rĂ©) en un seul aller-retour.
Exemple 1 : Création (CREATE)
Je veux créer un nouveau post. Je demande au serveur de me renvoyer l'id (généré) et le title du post créé.
# REQUĂTE CLIENT (POST sur /graphql)
mutation {
createPost(title: "Nouveau Post", content: "Contenu...") {
id
title
}
}
# RĂPONSE SERVEUR (JSON)
{
"data": {
"createPost": {
"id": "789", # ID généré par le serveur
"title": "Nouveau Post"
}
}
}
Exemple 2 : Mise Ă jour (UPDATE) avec un type input
Pour les mises Ă jour, on utilise des types input pour regrouper les arguments (voir 8.1).
Ajout au Schéma (schema.graphql) :
# Un "input" est comme un "type", mais pour les arguments
input UpdatePostInput {
title: String
content: String
}
# Ajout Ă "type Mutation"
type Mutation {
...
updatePost(id: ID!, input: UpdatePostInput!): Post
}
RequĂȘte de mise Ă jour (notez l'utilisation de variables) :
# REQUĂTE CLIENT
mutation UpdatePost($postId: ID!, $changes: UpdatePostInput!) {
updatePost(id: $postId, input: $changes) {
id
title
content
}
}
# VARIABLES JSON
{
"postId": "789",
"changes": {
"title": "Titre mis Ă jour"
}
}
Exemple 3 : Suppression (DELETE)
Une mutation de suppression renvoie souvent l'ID de l'objet supprimé ou un booléen de succÚs.
Ajout au Schéma (schema.graphql) :
type Mutation {
...
deletePost(id: ID!): ID # Renvoie l'ID du post supprimé
}
RequĂȘte de suppression :
mutation {
deletePost(id: "789")
}
# RĂPONSE SERVEUR
{ "data": { "deletePost": "789" } }
Les Queries et Mutations sont des opĂ©rations "demande/rĂ©ponse" classiques (pull). Le client demande, le serveur rĂ©pond. Mais que faire si le client veut ĂȘtre notifiĂ© par le serveur ? (ex: un chat en direct, une notification "nouveau commentaire").
C'est le rÎle des Subscriptions. Elles maintiennent une connexion longue durée (généralement via WebSockets) entre le client et le serveur.
Le Flux de Fonctionnement (Ex: avec Apollo PubSub)
- Le Client (ex: app React) envoie une requĂȘte
subscriptionau serveur GraphQL (ex: Apollo Server). - Le Serveur établit une connexion WebSocket.
- Le Client A "écoute" (s'abonne) à un "canal" (topic). (ex:
newComment(postId: "123")). - Le Client B exécute une
mutation(ex:addComment(postId: "123", ...)). - Le **Résolveur** de la mutation
addCommentfait 2 choses :
a. Il sauvegarde le commentaire en BDD.
b. Il "publie" le nouveau commentaire sur un canal PubSub (ex:pubsub.publish('COMMENT_ADDED', { newComment: leNouveauCommentaire })). - Le Serveur (qui écoutait ce canal) envoie le "push" (
leNouveauCommentaire) au Client A via le WebSocket.
Exemple de Code (Client & Schéma)
Schéma (schema.graphql) :
type Subscription {
# Ătre notifiĂ© (via WebSocket) d'un nouveau commentaire sur un post
newComment(postId: ID!): Comment
}
RequĂȘte Client (ex: avec Apollo Client) :
# Le client "s'abonne" à cet événement
subscription OnNewComment($postId: ID!) {
newComment(postId: $postId) {
# Quand un nouveau commentaire arrive, je veux ces champs
id
text
author {
username
}
}
}
Chaque fois qu'un commentaire est ajouté (via une mutation), le serveur poussera la donnée (id, text, author) au client, qui pourra mettre à jour l'interface graphique sans que l'utilisateur n'ait à rafraßchir la page.
Le Schéma est le "Quoi" (le contrat). Les Résolveurs sont le "Comment" (l'implémentation). Un résolveur est une fonction cÎté backend (JS, Java, Python) qui est "mappée" (liée) à un champ du Schéma.
Les 4 Arguments d'un Résolveur
Chaque fonction de résolveur reçoit 4 arguments : (parent, args, context, info).
| Argument | Description |
|---|---|
parent | Le résultat du résolveur parent. (ex: dans User.posts, parent est l'objet User qui a été trouvé juste avant). |
args | Un objet contenant les arguments passĂ©s Ă ce champ dans la requĂȘte (ex: { id: "123" }). |
context | Un objet partagĂ© entre *tous* les rĂ©solveurs d'une *mĂȘme* requĂȘte. C'est ici qu'on place l'info d'authentification (currentUser), les connexions BDD, etc. |
info | Un objet complexe contenant des infos sur la requĂȘte (AST). Surtout utilisĂ© pour l'optimisation avancĂ©e (savoir quels champs le client a *vraiment* demandĂ©). |
Exemple (avec context pour l'auth)
// Simule la création du serveur
const server = new ApolloServer({
typeDefs, // Le schéma
resolvers,
context: async ({ req }) => {
// Fonction qui crĂ©e le 'context' pour chaque requĂȘte
// 1. Lire le token "Authorization: Bearer ..."
const token = req.headers.authorization || '';
// 2. Trouver l'utilisateur
const currentUser = await getUserFromToken(token);
// 3. Le retourner. Il sera dispo dans tous les résolveurs.
return { currentUser, db };
}
});
// Dans vos résolveurs...
const resolvers = {
Query: {
me: (parent, args, context) => {
// 4. Utiliser le context !
if (!context.currentUser) {
throw new Error("Authentification requise !");
}
return context.currentUser;
}
},
Mutation: {
createPost: (parent, args, context) => {
// 5. Utiliser le context pour l'authentification
if (!context.currentUser) throw new Error("Non autorisé");
// 6. Utiliser le context pour la BDD
return context.db.posts.create({ ...args, authorId: context.currentUser.id });
}
}
};
Le "N+1 Query Problem" (ProblĂšme de Performance)
C'est le *principal* problÚme de performance de GraphQL (cÎté serveur).
Scénario : Le client demande 10 posts, et pour chaque post, l'auteur.
1. Le résolveur Query.allPosts s'exécute. (1 appel BDD : SELECT * FROM posts LIMIT 10).
2. Le serveur a 10 objets Post. Pour chacun, il doit résoudre Post.author.
3. Il exécute le résolveur Post.author... 10 fois. (10 appels BDD : SELECT * FROM users WHERE id = ...).
Total : 1 + 10 = 11 appels BDD. C'est un désastre.
Solution : DataLoader (de Facebook). C'est un outil qui "batch" (regroupe) et "cache" les requĂȘtes. Il intercepte les 10 demandes d'auteurs, les fusionne en une seule (SELECT * FROM users WHERE id IN (1, 2, 3...)) et redistribue les rĂ©sultats.
Le systÚme de types de GraphQL est le fondement de son Schéma. Il existe plusieurs catégories de types.
1. Types Scalaires (Types de base)
Ce sont les "feuilles" de l'arbre. Les données brutes. Il y en a 5 par défaut :
| Scalaire | Description | Exemple (JSON) |
|---|---|---|
String | ChaĂźne de caractĂšres (UTF-8). | "Bonjour" |
Int | Nombre entier signé 32 bits. | 123 |
Float | Nombre Ă virgule flottante. | 123.45 |
Boolean | Vrai ou faux. | true |
ID | Identifiant Unique. Sérialisé comme un String, mais indique que ce n'est pas "lisible par un humain". Sert aussi pour le cache (Apollo). | "1a2b3c" ou 123 |
2. Types Objets (type)
Ce sont les "nĆuds" de l'arbre. Des types que vous dĂ©finissez, qui ont leurs propres champs.
type User {
id: ID!
username: String
age: Int
}
3. Types d'Entrée (input)
Un input a la mĂȘme syntaxe qu'un type, mais il est utilisĂ© *exclusivement* comme argument pour les Mutations. Il regroupe les champs.
# Au lieu de :
# mutation createUser(username: String!, email: String!, age: Int)
# On fait :
input CreateUserInput {
username: String!
email: String!
age: Int
}
mutation {
createUser(input: CreateUserInput!): User
}
4. Modificateurs de Type (! et [])
Ce sont les plus importants. Ils "enveloppent" les autres types pour changer leur comportement.
| Modificateur | Exemple | Description |
|---|---|---|
Non-Null (!) | username: String! | Ce champ DOIT exister. Il ne peut jamais ĂȘtre null. Si le rĂ©solveur renvoie null, la requĂȘte *entiĂšre* plante. |
Liste ([]) | posts: [Post] | Ce champ renvoie une liste (un "array") de Post. La liste peut ĂȘtre null. Les Post dans la liste peuvent ĂȘtre null. |
| Liste Non-Nulle | posts: [Post]! | La liste doit exister (elle ne peut pas ĂȘtre null), mais elle peut ĂȘtre vide ([]). Les Post *dans* la liste peuvent ĂȘtre null. |
| Liste de Non-Nulls | posts: [Post!] | La liste peut ĂȘtre null. Mais si elle existe, elle ne peut *pas* contenir de null ([post1, null, post3] est interdit). |
| Liste Non-Nulle de Non-Nulls | posts: [Post!]! | La combinaison la plus stricte. La liste doit exister (!) et ne peut contenir que des Post valides (!). C'est le plus courant. |
ProblÚme : GraphQL est génial pour un monolithe. Mais en 2025, on utilise des microservices.
L'équipe A gÚre le service "Users" (avec son schéma GraphQL).
L'équipe B gÚre le service "Products" (avec son schéma GraphQL).
L'équipe C gÚre le service "Reviews" (avec son schéma GraphQL).
Comment le client (l'app mobile) peut-il faire une seule requĂȘte qui mĂ©lange les trois ? Doit-il connaĂźtre 3 endpoints GraphQL ?
Solution : Apollo Federation (Fédération).
Le Concept du "Supergraph"
La Fédération est une architecture qui consiste à :
- Avoir plusieurs serveurs GraphQL indépendants (des "subgraphs"), ex:
users,products. - Chaque "subgraph" étend les types des autres.
- Avoir une Gateway (Passerelle) unique, qui est le seul point d'entrée pour le client.
- La Gateway télécharge les schémas de tous les "subgraphs" et les compile en un "Supergraph" unifié.
- Quand une requĂȘte client arrive, la Gateway la dĂ©compose, l'envoie aux bons "subgraphs" en parallĂšle, et rĂ©-assemble la rĂ©ponse.
Exemple de "Federation" (Code)
C'est le concept le plus puissant de l'écosystÚme GraphQL moderne.
Service "Users" (http://...:4001)
# Schéma du service "Users"
type User @key(fields: "id") {
id: ID!
username: String
}
type Query {
user(id: ID!): User
}
Service "Products" (http://...:4002)
# Schéma du service "Products"
# Il "étend" le type User !
type User @key(fields: "id") @extends {
id: ID! @external
# Il ajoute un nouveau champ Ă User
purchasedProducts: [Product]
}
type Product {
id: ID!
name: String
}
Résultat pour le Client (via la Gateway) :
Le client ne voit qu'un seul schĂ©ma (le Supergraph) et peut faire une requĂȘte qui mĂ©lange les deux, sans savoir qu'ils sont sĂ©parĂ©s :
query {
user(id: "1") {
username # -> Résolu par le service Users
purchasedProducts { # -> Résolu par le service Products
name
}
}
}
1. Variables de RequĂȘte
Jusqu'à présent, nous avons "hardcodé" les IDs (ex: post(id: "123")). C'est trÚs mauvais :
1. SĂ©curitĂ© : C'est une porte ouverte Ă l'injection si on construit le string de requĂȘte Ă la main.
2. RĂ©utilisabilitĂ© : On ne peut pas rĂ©utiliser la mĂȘme requĂȘte pour l'ID "456".
La solution est d'utiliser des variables. La requĂȘte GraphQL (POST) est envoyĂ©e en 2 parties : le "query string" et un objet JSON "variables".
Exemple de RequĂȘte avec Variable
# 1. Le "Query String" (Le "template")
# - On "nomme" la query (ex: GetPostById)
# - On déclare la variable ($postId) et son type (ID!)
query GetPostById($postId: ID!) {
# On utilise la variable ($postId)
post(id: $postId) {
id
title
}
}
# 2. L'objet "Variables" (Le "contenu", envoyé en JSON séparé)
{
"postId": "123"
}
Le serveur GraphQL reçoit les deux, vĂ©rifie que $postId est bien un ID!, et "injecte" la variable dans la requĂȘte avant de l'exĂ©cuter. C'est 100% sĂ©curisĂ© et rĂ©utilisable.
2. Fragments
Les Fragments sont des "morceaux de requĂȘte" rĂ©utilisables.
ProblĂšme : J'ai deux requĂȘtes (newestPost et featuredPost) et pour les deux, je veux les *mĂȘmes* champs (id, titre, auteur). Je me rĂ©pĂšte (DRY).
Exemple de RequĂȘte avec Fragment
# 1. Définir le Fragment
# C'est un "morceau" de champs sur le type "Post"
fragment PostFields on Post {
id
title
author {
username
}
}
# 2. Utiliser le Fragment
query {
newestPost {
...PostFields # "étale" tous les champs du fragment ici
date
}
featuredPost {
...PostFields # "Ă©tale" les mĂȘmes champs ici
views
}
}
# 3. La requĂȘte finale (ce que le serveur "voit")
# query {
# newestPost {
# id
# title
# author { username }
# date
# }
# featuredPost {
# id
# title
# author { username }
# views
# }
# }
GraphQL n'est qu'une spécification (un "standard"). On ne "télécharge" pas GraphQL. On télécharge des outils (librairies) qui *implémentent* cette spécification. L'écosystÚme est dominé par Apollo.
| Outil | CÎté ? | Description |
|---|---|---|
| Apollo Server | Backend (Serveur) | La librairie Node.js (JavaScript) la plus populaire pour *construire* un serveur GraphQL. (C'est ce qui gÚre les Schémas, les Résolveurs, etc.). |
| Apollo Client | Frontend (Client) | La librairie (React, Vue, Angular, Mobile) la plus populaire pour *consommer* une API GraphQL. GÚre le cache, l'état (state), les Subscriptions... |
| Relay | Frontend (Client) | L'alternative à Apollo Client. Créé (et utilisé) par Facebook. Plus complexe, trÚs axé sur la performance et les fragments. |
GraphiQL : L'IDE indispensable
GraphiQL (notez le 'i') est un IDE (un outil de développement) qui tourne dans votre navigateur. La plupart des serveurs GraphQL (comme Apollo Server) l'activent automatiquement en mode "développement".
C'est l'outil n°1 pour tester et débugger une API GraphQL.
Il offre :
- Ăditeur de RequĂȘte (Gauche) : Avec auto-complĂ©tion *totale* (il lit le SchĂ©ma !).
- Panneau de Variables (Bas Gauche) : Pour tester vos variables JSON.
- FenĂȘtre de RĂ©sultat (Droite) : Affiche la rĂ©ponse JSON (ou l'erreur).
- "Docs" (ExtrĂȘme Droite) : L'introspection.
C'est la fonction la plus puissante. Le GraphiQL "interroge" l'API sur son propre Schéma (une "introspection query"). Il génÚre une documentation *interactive* et *toujours à jour* de 100% de l'API (tous les types, queries, mutations, et leurs arguments). C'est la fin de la "documentation d'API obsolÚte".
Références Extérieures (Liens)
- GraphQL.org (La Fondation) La documentation officielle de la spécification et les concepts de base.
- Apollo GraphQL Docs La documentation complÚte d'Apollo (Client, Server, Federation). La référence de l'écosystÚme.
- How to GraphQL Le meilleur tutoriel "full-stack" (React, Node, etc.) pour apprendre de zéro.
SDL (Schema Definition Language)
# ---- Types de Base ----
scalar Date # Définit un type feuille custom
type User { ... } # Définit un objet
interface Node { # Définit un contrat (polymorphisme)
id: ID!
}
type Post implements Node { # Implémente le contrat
id: ID!
title: String
}
enum Role { # Liste de choix
USER
ADMIN
}
input CreateUserInput { ... } # Objet pour arguments
union SearchResult = User | Post # Peut ĂȘtre l'un OU l'autre
# ---- Modificateurs ----
field: String! # Non-Null
field: [String] # Liste (nullable) de Strings (nullable)
field: [String]! # Liste NON-NULLE de Strings (nullable)
field: [String!] # Liste (nullable) de Strings NON-NULLS
field: [String!]! # Liste NON-NULLE de Strings NON-NULLS
# ---- Points d'Entrée ----
type Query {
# (args): ReturnType
user(id: ID!): User
}
type Mutation {
createUser(input: CreateUserInput!): User
}
type Subscription {
newUser: User
}
# ---- Dépréciation ----
type User {
name: String @deprecated(reason: "Utiliser 'username'")
username: String
}
Syntaxe Query (Client)
# ---- Opérations ----
query MyQueryName { ... }
mutation MyMutationName { ... }
subscription MySubName { ... }
# ---- Variables ($) ----
query GetUser($userID: ID!, $withPosts: Boolean!) {
user(id: $userID) {
id
username
}
}
# JSON de variables:
# { "userID": "123", "withPosts": true }
# ---- Fragments (Réutilisation) ----
fragment UserFields on User {
id
username
}
query {
user1: user(id: "1") { ...UserFields }
user2: user(id: "2") { ...UserFields }
}
# ---- Aliases ----
query {
admin: user(id: "1") { name }
guest: user(id: "2") { name }
}
# ---- Directives (Logique client) ----
# @include(if: Boolean) -> Inclut si 'true'
# @skip(if: Boolean) -> Saute si 'true'
query GetUser($withPosts: Boolean!) {
user(id: "1") {
username
# Le champ 'posts' ne sera inclus que
# si la variable $withPosts est 'true'
posts @include(if: $withPosts) {
title
}
}
}
# ---- Fragments "inline" (Polymorphisme) ----
# Si SearchResult peut ĂȘtre User OU Post
query {
search(text: "hello") {
__typename # Champ magique: renvoie le nom du type
... on User {
username
}
... on Post {
title
}
}
}
