Qu’est-ce qu’Express ?
Express est un framework web minimaliste pour Node.js. Il ne vous impose ni structure de dossiers, ni base de données, ni architecture. Il vous fournit trois éléments essentiels et vous laisse ensuite libre de vos choix : un moyen de faire correspondre des URLs, un objet requête (request) et un objet réponse (response), ainsi qu’une chaîne de middleware pour lier le tout.
Cette sobriété est volontaire. Express est apparu en 2010, à une époque où le module http natif de Node nécessitait d’écrire manuellement le routage et l’analyse du corps des requêtes (body parsing). Express a réduit ces tâches à quelques lignes de code. Quinze ans plus tard, il reste le choix par défaut pour les API Node, et ses concepts ont été repris par presque tous les frameworks qui ont suivi.
Si vous comprenez Express, vous comprenez la structure même du JavaScript côté serveur.
Le cycle requête-réponse
Dans son essence, une application Express est une fonction qui reçoit une requête et envoie une réponse. Tout le reste n’est que du sucre syntaxique pour faciliter le développement.
import express from "express";
const app = express();
app.get("/", (req, res) => {
res.send("Hello, world");
});
L’objet req encapsule le message entrant de Node.js : req.params, req.query, req.body et req.headers vous fournissent les données d’entrée. L’objet res encapsule le message sortant : res.status(), res.json(), res.send() et res.set() façonnent la sortie. Un handler met fin à la requête en appelant l’une des méthodes res.
Routage
Une route est composée d’une méthode, d’un chemin et d’un ou plusieurs handlers. Les chemins peuvent être statiques, paramétrés avec :name, ou correspondre à des patterns.
app.get("/posts", listPosts);
app.get("/posts/:id", getPost);
app.post("/posts", createPost);
app.put("/posts/:id", replacePost);
app.patch("/posts/:id", updatePost);
app.delete("/posts/:id", deletePost);
Les paramètres de route arrivent dans req.params, et les query strings dans req.query.
app.get("/posts/:id", (req, res) => {
const { id } = req.params; // "/posts/42" -> "42"
const { fields } = req.query; // "?fields=title" -> "title"
res.json({ id, fields });
});
Vous pouvez également passer plusieurs handlers à une seule route. Ils s’exécutent dans l’ordre jusqu’à ce que l’un d’entre eux termine la réponse ; c’est ainsi que vous attachez des middleware par route, comme la validation ou l’autorisation.
app.post("/posts", requireAuth, validatePost, createPost);
Middleware : le concept essentiel à maîtriser
Le middleware est le cœur d’Express. Il s’agit de toute fonction qui reçoit (req, res, next) et qui, soit termine la réponse, soit appelle next() pour continuer.
function logger(req, res, next) {
const start = Date.now();
res.on("finish", () => {
console.log(`${req.method} ${req.url} ${res.statusCode} ${Date.now() - start}ms`);
});
next();
}
app.use(logger);
Le middleware s’exécute dans l’ordre où il est enregistré. Cette règle unique explique la majeure partie du comportement d’Express : enregistrez le parseur JSON avant les routes qui lisent req.body, enregistrez l’authentification avant les routes protégées, et enregistrez le gestionnaire de 404 après toutes les autres routes.
Il existe trois types qu’il est utile de connaître :
- Application middleware —
app.use(fn)s’exécute pour chaque requête. - Router middleware —
router.use(fn)s’exécute uniquement pour les chemins de ce routeur. - Route middleware —
app.get(path, fn, handler)s’exécute uniquement pour cette route.
Un middleware qui prend quatre arguments est un error handler et ne s’exécute que lorsqu’une fonction appelle next(err).
Les routeurs permettent de garder les applications volumineuses lisibles
À mesure qu’une application s’agrandit, un fichier unique de routes devient ingérable. express.Router() vous permet de regrouper les routes liées et de les monter sous un préfixe.
// routes/posts.js
import { Router } from "express";
const router = Router();
router.get("/", listPosts);
router.post("/", createPost);
export default router;
// app.js
import posts from "./routes/posts.js";
app.use("/api/v1/posts", posts);
Désormais, chaque fichier gère une seule ressource, et le préfixe d’URL est défini à un seul endroit. C’est cette structure qui empêche les projets Express de se transformer en un amas d’endpoints.
Gestion des erreurs
Les erreurs doivent converger vers un point unique. Appelez next(err) depuis n’importe où et Express sautera directement au premier gestionnaire d’erreurs à quatre arguments.
app.get("/users/:id", async (req, res, next) => {
try {
const user = await db.user.findById(req.params.id);
if (!user) return res.status(404).json({ error: "not_found" });
res.json(user);
} catch (err) {
next(err);
}
});
app.use((err, req, res, next) => {
console.error(err);
res.status(err.status ?? 500).json({
error: err.code ?? "internal_error",
message: err.message,
});
});
Express 5 transfère automatiquement les promesses rejetées vers le gestionnaire d’erreurs, les gestionnaires async n’ont donc plus besoin d’un wrapper. Cependant, être explicite avec try/catch rend l’intention évidente et permet de garder le code portable. Enregistrez un gestionnaire 404 après toutes les routes afin que les chemins non correspondants retournent une erreur JSON appropriée au lieu de la page HTML par défaut d’Express.
Lire le corps de la requête
Express ne parse pas les corps de requête par défaut. Ajoutez les parsers intégrés avant les routes qui en ont besoin.
app.use(express.json()); // application/json
app.use(express.urlencoded({ extended: true })); // form posts
Pour l’upload de fichiers, utilisez multer. Pour une validation plus poussée, combinez le parsing avec une bibliothèque de schémas comme Zod ou Joi afin de rejeter les entrées invalides rapidement, avant qu’elles n’atteignent vos handlers.
Bases de la sécurité et de la production
Express est volontairement minimaliste, ainsi, quelques lignes de middleware permettent de couvrir l’essentiel :
import helmet from "helmet";
import cors from "cors";
import rateLimit from "express-rate-limit";
app.use(helmet()); // sensible security headers
app.use(cors({ origin: "https://app.example.com" }));
app.use(rateLimit({ windowMs: 60_000, max: 100 }));
Désactivez également x-powered-by, maintenez vos dépendances à jour, validez et limitez la taille des requêtes, et ne faites jamais confiance aux entrées du client. Ce sont les mêmes préoccupations pour tout backend ; Express vous offre simplement un endroit restreint et bien connu pour gérer chacun de ces points.
Tests
Comme les handlers sont de simples fonctions, Express est facile à tester. supertest démarre l’application dans le processus actuel et permet d’effectuer de réelles assertions HTTP sans ouvrir de port.
import request from "supertest";
import app from "../app.js";
test("GET /posts returns a list", async () => {
const res = await request(app).get("/posts").expect(200);
expect(Array.isArray(res.body)).toBe(true);
});
Séparez la définition de l’application (app.js) du démarrage du serveur (server.js) afin que les tests puissent importer l’application sans lancer l’écoute. Cette simple séparation rend les tests d’intégration triviaux.
Bonnes pratiques
- Gardez vos route handlers légers ; déplacez la logique dans des services et des couches d’accès aux données.
- Divisez vos routes par fonctionnalité avec
express.Router()et montez-les sous un préfixe de version. - Enregistrez vos middleware de manière réfléchie et dans l’ordre : les parsers, puis l’auth, puis les routes, puis le 404, et enfin les erreurs.
- Transmettez chaque erreur avec
next(err)et formatez les réponses à un seul endroit. - Validez et limitez toutes les entrées avant qu’elles n’atteignent la logique métier.
- Utilisez
helmet,corset le rate limiting dès le premier jour. - Séparez l’application du serveur pour que les tests restent rapides.
Erreurs courantes
- Oublier
express.json()et se demander pourquoireq.bodyest undefined. - Négliger les promesses rejetées dans les handlers async sur Express 4.
- Enregistrer le handler 404 ou le gestionnaire d’erreurs avant les routes qu’ils sont censés intercepter.
- Laisser un seul fichier de routes s’étendre sur des centaines de lignes.
- Retourner un code 200 pour des erreurs au lieu d’utiliser les codes de statut appropriés.
- Faire confiance à
req.bodysans validation. - Mélanger
res.send,res.jsonetres.endde manière imprévisible.
Et après ?
Express vous a enseigné le fonctionnement des requêtes, des réponses et de la chaîne de middleware — des concepts que vous réutiliserez tout au long de votre carrière. Si vous recherchez plus de performance et une validation intégrée, consultez ensuite le guide Fastify. Si vous avez besoin de structure et d’injection de dépendances pour une équipe nombreuse, tournez-vous vers NestJS. Pour les déploiements edge et serverless, Hono propose le même style sur un runtime basé sur les standards du web. Et si tout cela vous a semblé aller trop vite, n’hésitez pas à revoir les bases de Node.js qui sous-tendent tout ceci.