Créer une API REST avec Express.js
Express.js est le framework web le plus utilisé de l’écosystème Node.js. Framework minimaliste, il ne vous impose presque aucune structure : il se contente de simplifier radicalement la gestion des routes, des requêtes et des réponses, là où le module natif http de Node.js devient vite verbeux. Si vous savez déjà lancer un serveur Node, Express est la brique qui vous fera passer à une véritable API. Ce guide construit une API REST fonctionnelle, pas à pas.
Installer et démarrer Express
Express s’installe comme n’importe quel paquet npm, dans un projet déjà initialisé avec npm init :
npm install express
Le serveur minimal tient en quelques lignes. Dans un fichier server.js :
import express from "express";
const app = express();
app.use(express.json()); // permet de lire les corps JSON
app.get("/", (requete, reponse) => {
reponse.send("API en ligne");
});
app.listen(3000, () => {
console.log("Serveur sur http://localhost:3000");
});
Comparé à un serveur écrit avec le module http brut, la différence saute aux yeux : plus besoin de vérifier manuellement l’URL et la méthode, Express s’en charge. La ligne app.use(express.json()) est un middleware intégré qui analyse automatiquement le corps JSON des requêtes entrantes — nous y reviendrons.
Les routes et les méthodes HTTP
Une route associe une URL et une méthode HTTP à une fonction de traitement. Express expose une méthode par verbe : app.get, app.post, app.put, app.patch, app.delete. Construisons une API pour gérer une collection d’articles :
// données en mémoire, pour l'exemple
let articles = [
{ id: 1, titre: "Débuter avec Node.js" },
{ id: 2, titre: "Comprendre REST" }
];
// Lire la collection
app.get("/articles", (req, res) => {
res.json(articles);
});
// Lire un article précis
app.get("/articles/:id", (req, res) => {
const article = articles.find(a => a.id === Number(req.params.id));
if (!article) {
return res.status(404).json({ erreur: "Article introuvable" });
}
res.json(article);
});
La partie :id dans l’URL est un paramètre de route : Express la rend accessible via req.params.id. On récupère ici l’identifiant, on cherche l’article correspondant, et on renvoie une erreur 404 s’il n’existe pas. Cette correspondance entre verbes HTTP et opérations forme le cœur d’une API REST, un sujet que détaille notre guide sur les principes d’une API REST.
Les objets req et res
Chaque fonction de route reçoit deux objets essentiels. La requête (req) décrit ce que le client envoie, la réponse (res) est ce que vous renvoyez.
Du côté de req, les propriétés les plus utiles sont :
req.params— les paramètres d’URL (/articles/:id).req.query— la query string (/articles?tri=date&page=2).req.body— le corps de la requête, disponible grâce au middlewareexpress.json().req.headers— les en-têtes HTTP.
Du côté de res, on enchaîne généralement un code de statut et un contenu :
res.status(201).json({ id: 3, titre: "Nouvel article" });
res.json(...)envoie une réponse JSON avec le bon en-têteContent-Type.res.status(code)fixe le code de statut HTTP.res.send(...)envoie du texte ou du HTML.
Créer et modifier des ressources
Voyons maintenant la création et la mise à jour, qui exploitent req.body :
// Créer un article
app.post("/articles", (req, res) => {
const { titre } = req.body;
if (!titre) {
return res.status(400).json({ erreur: "Le titre est obligatoire" });
}
const nouvel = { id: Date.now(), titre };
articles.push(nouvel);
res.status(201).json(nouvel);
});
// Modifier un article
app.patch("/articles/:id", (req, res) => {
const article = articles.find(a => a.id === Number(req.params.id));
if (!article) {
return res.status(404).json({ erreur: "Article introuvable" });
}
if (req.body.titre) article.titre = req.body.titre;
res.json(article);
});
// Supprimer un article
app.delete("/articles/:id", (req, res) => {
articles = articles.filter(a => a.id !== Number(req.params.id));
res.status(204).end();
});
Remarquez les codes de statut choisis avec soin : 201 Created après une création, 400 Bad Request en cas de données invalides, 404 Not Found quand la ressource n’existe pas, 204 No Content après une suppression réussie. Une API honnête reflète toujours le résultat dans son code de statut.
Les middlewares, cœur d’Express
Un middleware est une fonction qui s’exécute entre la réception d’une requête et l’envoi de la réponse. Elle reçoit trois arguments : req, res et next, cette dernière permettant de passer au middleware suivant. C’est le mécanisme central d’Express : l’authentification, la journalisation, la validation ou la gestion CORS sont tous des middlewares.
Un exemple simple qui journalise chaque requête :
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next(); // sans cet appel, la requête reste bloquée
});
Placé avant les routes, ce middleware s’exécute pour chaque requête. L’oubli de next() est une erreur classique : sans lui, la requête n’atteint jamais la route et le client attend indéfiniment.
Gérer les erreurs proprement
Express dispose d’un type particulier de middleware pour les erreurs, reconnaissable à ses quatre arguments. Placé en dernier, il capture toutes les erreurs survenues dans les routes :
app.use((erreur, req, res, next) => {
console.error(erreur.stack);
res.status(erreur.status || 500).json({
erreur: erreur.message || "Erreur interne du serveur"
});
});
Pour l’utiliser, il suffit de passer une erreur à next() dans une route, ou de lever une exception dans une fonction asynchrone correctement encadrée. Centraliser la gestion des erreurs évite de répéter la même logique dans chaque route.
Structurer son projet
Tout mettre dans un seul server.js fonctionne pour apprendre, mais devient vite ingérable. Une structure éprouvée sépare les responsabilités :
mon-api/
├── server.js # point d'entrée, configuration Express
├── routes/
│ └── articles.js # les routes liées aux articles
├── controllers/
│ └── articles.js # la logique métier
└── package.json
Express propose l’objet Router pour découper les routes par domaine :
// routes/articles.js
import { Router } from "express";
const router = Router();
router.get("/", listerArticles);
router.post("/", creerArticle);
export default router;
// server.js
import articlesRouter from "./routes/articles.js";
app.use("/articles", articlesRouter);
Toutes les routes du fichier articles.js sont ainsi préfixées par /articles, ce qui garde le point d’entrée lisible même quand l’API grandit.
Connecter une base de données
Nos exemples stockaient les données dans un tableau en mémoire — elles disparaissent au redémarrage. En production, on connecte une vraie base. La logique reste identique : on remplace les opérations sur le tableau par des appels à la base, généralement asynchrones :
app.get("/articles", async (req, res, next) => {
try {
const articles = await db.query("SELECT * FROM articles");
res.json(articles);
} catch (erreur) {
next(erreur); // délègue au middleware d'erreur
}
});
Le bloc try/catch combiné à next(erreur) est le motif standard pour gérer les erreurs asynchrones. En pratique, on passe souvent par un ORM comme Prisma ou Sequelize, qui traduit ces requêtes en appels JavaScript plus lisibles. Le point clé : Express ne se soucie pas de la base utilisée, il orchestre simplement requêtes et réponses.
En résumé
Express transforme Node.js en une plateforme d’API productive avec très peu de cérémonie : des routes claires par verbe HTTP, des middlewares composables, des objets req et res expressifs, et une gestion des erreurs centralisée. Commencez petit avec un seul fichier, puis découpez en routes et contrôleurs à mesure que le projet grossit. C’est cette simplicité assumée qui explique la longévité d’Express dans l’écosystème JavaScript.
À lire ensuite
- API REST : principes et bonnes pratiques — les conventions à respecter pour concevoir de bonnes API.
- Le CRUD expliqué : Create, Read, Update, Delete — les quatre opérations que votre API implémente.
- Node.js : débuter le développement côté serveur — revenir aux fondations si besoin.