GraphQL est un langage de requête pour les APIs, créé par Facebook en 2012 et open-sourcé en 2015. Contrairement à REST (plusieurs endpoints), GraphQL expose un seul endpoint et c’est le client qui décrit exactement les données dont il a besoin.
GraphQL repose sur HTTP (généralement
POST /graphql). Pour le protocole HTTP, voir Requêtes HTTP et HTTPS.
Le problème que GraphQL résout
Problème REST n°1 — Over-fetching :
GET /users/42
→ { id, name, email, address, phone, preferences, avatar, ... }
Le client voulait juste name et email → 80% des données inutiles
Problème REST n°2 — Under-fetching (N+1 requests) :
GET /users/42 → { id, name, orders: [1, 2, 3] }
GET /orders/1 → { ... }
GET /orders/2 → { ... }
GET /orders/3 → { ... }
4 requêtes pour afficher le profil d'un utilisateur avec ses commandes
GraphQL :
POST /graphql
{
user(id: "42") {
name
email
orders {
id
total
}
}
}
→ 1 seule requête, exactement les champs voulus
Schéma (SDL — Schema Definition Language)
Le schéma est le contrat entre client et serveur. Il définit les types, les relations et les opérations disponibles.
# Définition des types
type User {
id: ID! # ! = non-null (obligatoire)
name: String!
email: String!
role: Role!
orders: [Order!]! # liste non-null d'Order non-null
createdAt: String
}
type Order {
id: ID!
total: Float!
status: OrderStatus!
items: [OrderItem!]!
}
enum Role {
ADMIN
VIEWER
EDITOR
}
enum OrderStatus {
PENDING
SHIPPED
DELIVERED
}
# Types racine
type Query {
user(id: ID!): User
users(role: Role, limit: Int): [User!]!
}
type Mutation {
createUser(name: String!, email: String!, role: Role!): User!
updateUser(id: ID!, role: Role): User!
deleteUser(id: ID!): Boolean!
}
type Subscription {
orderStatusChanged(orderId: ID!): Order!
}Les 3 types d’opérations
Query — lire des données
# Requête simple
query {
user(id: "42") {
name
email
}
}
# Requête avec variables (bonne pratique)
query GetUser($userId: ID!) {
user(id: $userId) {
name
email
orders {
id
total
status
}
}
}
# Variables : { "userId": "42" }
# Requête multiple en une seule
query Dashboard($userId: ID!) {
user(id: $userId) { name }
users(role: ADMIN, limit: 5) { id name }
}Mutation — modifier des données
mutation CreateUser($input: CreateUserInput!) {
createUser(name: $input.name, email: $input.email, role: $input.role) {
id
name
createdAt
}
}
# Variables : { "input": { "name": "Alice", "email": "alice@ex.com", "role": "ADMIN" } }Subscription — données en temps réel
subscription TrackOrder($orderId: ID!) {
orderStatusChanged(orderId: $orderId) {
id
status
updatedAt
}
}
# Le client reçoit une notification à chaque changement de statut
# Repose sur WebSocket ou SSEResolvers
Chaque champ du schéma est résolu par une fonction resolver côté serveur. Le client ne voit pas les resolvers.
// Resolver (Node.js / Apollo Server)
const resolvers = {
Query: {
user: (parent, args, context) => {
// args.id = "42" (argument passé dans la requête)
return db.users.findById(args.id);
},
users: (parent, args, context) => {
return db.users.findAll({ role: args.role, limit: args.limit });
},
},
User: {
// Resolver de champ imbriqué
orders: (user, args, context) => {
return db.orders.findByUserId(user.id); // N+1 potentiel → utiliser DataLoader
},
},
Mutation: {
createUser: (parent, args, context) => {
if (!context.user.isAdmin) throw new Error("Forbidden");
return db.users.create(args);
},
},
};Fragments — réutiliser des sélections
fragment UserBasic on User {
id
name
email
}
query {
user(id: "1") { ...UserBasic }
users { ...UserBasic }
}GraphQL vs REST
| Critère | REST | GraphQL |
|---|---|---|
| Endpoints | Un par ressource (/users, /orders) | Un seul (/graphql) |
| Over-fetching | Fréquent (champs fixes) | Inexistant (client choisit) |
| Under-fetching | Fréquent (N+1 requêtes) | Inexistant (requêtes imbriquées) |
| Versioning | Nécessaire (/v1, /v2) | Évolution du schéma sans version |
| Caching HTTP | Natif (GET cacheable) | Difficile (tout en POST) |
| Introspection | Swagger/OpenAPI externe | Intégrée au protocole |
| Courbe d’apprentissage | Faible | Modérée |
| Idéal pour | APIs publiques simples | Frontend riche, mobile |
Le problème N+1 et DataLoader
Quand un resolver fait une requête DB pour chaque élément d’une liste, on crée un problème N+1 :
Query : users { orders { ... } }
Sans DataLoader :
SELECT * FROM users → 3 users
SELECT * FROM orders WHERE user_id = 1
SELECT * FROM orders WHERE user_id = 2
SELECT * FROM orders WHERE user_id = 3 ← N+1 requêtes
Avec DataLoader (batching) :
SELECT * FROM users → 3 users
SELECT * FROM orders WHERE user_id IN (1, 2, 3) ← 1 seule requête
Authentification et autorisation
# L'authentification se fait via les headers HTTP, comme REST
POST /graphql
Authorization: Bearer eyJ...
# L'autorisation se gère dans les resolvers ou avec des directives
type Query {
adminDashboard: Stats @auth(requires: ADMIN)
}En relation avec
- API — Vue d’ensemble — comparatif des types d’API
- API REST — alternative à GraphQL pour les APIs simples
- API WebSocket — GraphQL subscriptions repose sur WebSocket
- Authentification API — sécuriser les endpoints GraphQL
- Requêtes HTTP et HTTPS — GraphQL repose sur HTTP POST