Pourquoi Express avec GraphQL ?
Dans un monde où les applications web sont de plus en plus complexes, il est crucial de pouvoir récupérer et manipuler des données de manière flexible et efficace. C'est là que GraphQL entre en jeu. GraphQL est une spécification pour une API graphique qui permet aux clients d'interroger exactement ce qu'ils veulent et de manière performante.
En utilisant Express, un framework Node.js populaire, nous pouvons créer une API GraphQL robuste et performante. Cette combinaison offre plusieurs avantages :
- Contrôle total sur les données : Les clients peuvent spécifier exactement quels champs ils veulent récupérer, réduisant ainsi le volume de données transférées.
- Performance optimisée : Les requêtes GraphQL sont évaluées et exécutées en une seule passe, ce qui permet d'optimiser les performances en évitant les requêtes multiples au serveur.
- Facilité de maintenance : En utilisant un schéma centralisé des données, le code est plus facile à maintenir et à évoluer.
Un cas concret d'utilisation serait une application e-commerce où les clients peuvent récupérer non seulement les détails des produits mais aussi leurs avis, images, prix en fonction de leur demande. Avec GraphQL, cela peut être réalisé en une seule requête, tandis qu'avec une API REST traditionnelle, plusieurs requêtes pourraient être nécessaires.
Prerequis
Connaissances
- JavaScript et Node.js
- Base de données (par exemple MongoDB ou PostgreSQL)
- Principe des APIs et RESTful
- Connaissance de base de GraphQL
Outils à installer
- Node.js (v14 ou plus récent)
- npm (Node Package Manager) pour gérer les dépendances
- Code editor comme VSCode
Concepts fondamentaux
Schéma GraphQL
Le schéma GraphQL définit l'API. Il décrit les objets, leurs champs et comment ils sont liés entre eux.
## schema.graphql
type Query {
hello: String
}
Résolveurs
Les résolveurs sont des fonctions qui renvoient les données demandées par un champ du schéma.
// resolvers.js
const resolvers = {
Query: {
hello: () => 'Hello, world!'
}
};
module.exports = resolvers;
Serveur Express
Nous allons créer un serveur Express pour exposer notre API GraphQL.
// server.js
const express = require('express');
const { ApolloServer, gql } = require('apollo-server-express');
// Schéma GraphQL
const typeDefs = gql`
type Query {
hello: String
}
`;
// Résolveurs
const resolvers = {
Query: {
hello: () => 'Hello, world!'
}
};
const app = express();
const server = new ApolloServer({ typeDefs, resolvers });
server.applyMiddleware({ app });
app.listen({ port: 4000 }, () =>
console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`)
);
Introspection
L'introspection est une fonctionnalité de GraphQL qui permet aux clients d'explorer le schéma.
curl -X POST \
'http://localhost:4000/graphql' \
-H 'Content-Type: application/json' \
--data-raw '{"query": "__schema"}'
Mise en pratique : projet fil rouge
Nous allons construire un mini-projet complet : une API de gestion de tâches. L'application permettra aux utilisateurs de créer, lire, mettre à jour et supprimer des tâches.
Étape 1 : Initialisation du projet
mkdir todo-graphql
cd todo-graphql
npm init -y
Étape 2 : Installation des dépendances
npm install express apollo-server-express graphql mongoose
Étape 3 : Configuration de la base de données
Créez un fichier models/Task.js pour définir le modèle de tâche.
// models/Task.js
const mongoose = require('mongoose');
const taskSchema = new mongoose.Schema({
title: String,
description: String,
completed: Boolean,
createdAt: { type: Date, default: Date.now }
});
module.exports = mongoose.model('Task', taskSchema);
Étape 4 : Schéma GraphQL
Créez un fichier schema.graphql pour définir le schéma de l'API.
## schema.graphql
type Task {
id: ID!
title: String!
description: String
completed: Boolean!
createdAt: String!
}
type Query {
tasks: [Task]
task(id: ID!): Task
}
type Mutation {
createTask(title: String!, description: String): Task
updateTask(id: ID!, title: String, description: String, completed: Boolean): Task
deleteTask(id: ID!): Task
}
Étape 5 : Résolveurs
Créez un fichier resolvers.js pour définir les résolveurs.
// resolvers.js
const mongoose = require('mongoose');
const Task = require('../models/Task');
const resolvers = {
Query: {
tasks: async () => await Task.find(),
task: async (_, { id }) => await Task.findById(id)
},
Mutation: {
createTask: async (_, { title, description }) => {
const newTask = new Task({ title, description });
return await newTask.save();
},
updateTask: async (_, { id, title, description, completed }) => {
return await Task.findByIdAndUpdate(id, { title, description, completed }, { new: true });
},
deleteTask: async (_, { id }) => {
return await Task.findByIdAndDelete(id);
}
}
};
module.exports = resolvers;
Étape 6 : Serveur Express
Créez un fichier server.js pour démarrer le serveur.
// server.js
const express = require('express');
const { ApolloServer, gql } = require('apollo-server-express');
const typeDefs = require('./schema.graphql');
const resolvers = require('./resolvers');
mongoose.connect('mongodb://localhost:27017/todo', {
useNewUrlParser: true,
useUnifiedTopology: true
});
const app = express();
const server = new ApolloServer({ typeDefs, resolvers });
server.applyMiddleware({ app });
app.listen({ port: 4000 }, () =>
console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`)
);
Étape 7 : Test de l'API
Vous pouvez tester les requêtes GraphQL via le navigateur ou en utilisant curl.
## Créer une tâche
curl -X POST \
'http://localhost:4000/graphql' \
-H 'Content-Type: application/json' \
--data-raw '{"query": "mutation { createTask(title: \"Faire le ménage\", description: \"Nettoyer la maison\") { id title description completed createdAt } }"}'
## Lire toutes les tâches
curl -X POST \
'http://localhost:4000/graphql' \
-H 'Content-Type: application/json' \
--data-raw '{"query": "query { tasks { id title description completed createdAt } }"}'
Erreurs fréquentes et debugging
Erreur 1 : Cannot query field "tasks" on type "Query"
Cette erreur se produit si le champ tasks n'est pas défini dans le schéma.
## ❌ Mauvais
type Query {
hello: String
}
## ✅ Correct
type Query {
tasks: [Task]
}
Erreur 2 : TypeError: Cannot read property 'title' of undefined
Cette erreur se produit si la requête GraphQL tente d'accéder à un champ inexistant sur un objet.
## ❌ Mauvais
query {
task(id: "60d1c4f8b3a57e2b9c1e6b3b") {
id title description completed createdAt
}
}
## ✅ Correct
query {
task(id: "60d1c4f8b3a57e2b9c1e6b3b") {
id title description completed createdAt
}
}
Erreur 3 : Error in parsing GraphQL request body
Cette erreur se produit si la requête GraphQL est mal formatée.
## ❌ Mauvais
mutation {
createTask(title: "Faire le ménage", description: "Nettoyer la maison")
}
## ✅ Correct
mutation {
createTask(input: { title: "Faire le ménage", description: "Nettoyer la maison" }) {
id title description completed createdAt
}
}
Pour aller plus loin
1. Authentification et sécurité
Ajoutez une couche d'authentification pour sécuriser vos requêtes GraphQL.
- Utilisez des tokens JWT (JSON Web Tokens).
- Implémentez des middlewares pour vérifier les tokens avant l'exécution des mutations et des requêtes protégées.
2. Pagination et filtres
Ajoutez la pagination et les filtres aux requêtes GraphQL pour gérer de grandes ensembles de données efficacement.
type Query {
tasks(first: Int, after: String): [Task]!
}
3. Subscriptions
Ajoutez des souscriptions à votre API GraphQL pour permettre une communication bidirectionnelle entre le serveur et les clients.
type Subscription {
taskAdded: Task
}
Défi pratique
Implémentez un système de notifications dans l'API GraphQL, en utilisant des subscriptions pour envoyer des notifications lorsque une nouvelle tâche est ajoutée ou mise à jour.