L'essentiel en une minute
Créer une API REST Node.js en 2026, c'est d'abord choisir les bonnes versions : Node.js 24 est la LTS la plus récente au 24 octobre 2026, et Express 5.3.0 est sortie le 9 octobre. La première version de cet article reposait sur Node.js 20, arrivé en fin de vie le 30 avril 2026 : tout le code a été réécrit.
Ce tutoriel construit une petite API de gestion de tâches, de l'initialisation au déploiement. Il s'appuie sur la documentation de Node.js et d'Express, sur MDN pour HTTP et sur l'OWASP pour la sécurité, consultées le 24 octobre 2026. Le code suit ces documentations, mais nous ne l'avons pas exécuté.
Points clés
- Visez Node.js 24 LTS aujourd'hui, ou Node.js 26 à partir du 28 octobre 2026, date à laquelle il doit passer en LTS selon le calendrier officiel. Express 5 exige Node.js 18 au minimum.
- Express 5 transmet automatiquement les erreurs des fonctions
asyncau gestionnaire d'erreurs : plus besoin d'envelopper chaque route dans untry/catch.- Le module
node:sqlite, intégré à Node.js, suffit pour apprendre sans dépendance, mais il est encore au stade « release candidate » et synchrone : passez à PostgreSQL en production.- Pour l'authentification, l'OWASP demande de refuser
alg: none, de vérifieriss,audetexp, et de hacher les mots de passe avec Argon2id, ou à défaut scrypt.- Le lanceur de tests intégré à Node.js, stable depuis la version 20, et
fetchsuffisent pour tester une API REST Node.js sans framework de test.
Les principes REST et les codes de statut
REST n'est pas une norme mais un ensemble de contraintes d'architecture. MDN précise qu'un débutant peut voir une API REST comme un service HTTP appelable avec les outils web standard (glossaire MDN, REST, mis à jour le 11 juillet 2025). En pratique : chaque ressource a une URL (/api/v1/taches/42), le verbe HTTP dit ce qu'on en fait, le code de statut dit comment ça s'est passé.
| Verbe | Usage dans notre API | Idempotent | Code en cas de succès |
|---|---|---|---|
| GET | Lire la liste ou une tâche | Oui | 200 |
| POST | Créer une tâche | Non | 201 |
| PUT | Remplacer une tâche entière | Oui | 200 |
| PATCH | Modifier une partie d'une tâche | Non garanti | 200 |
| DELETE | Supprimer une tâche | Oui | 204 |
Selon MDN, les méthodes sûres, PUT et DELETE sont idempotentes, alors que POST et PATCH ne le sont pas forcément (glossaire MDN, Idempotent) : deux PUT identiques laissent la ressource dans le même état, deux POST créent deux tâches.
Les codes de statut forment cinq classes : informations (1xx), succès (2xx), redirections (3xx), erreurs du client (4xx) et du serveur (5xx), selon la référence MDN des codes de statut. Voici ceux de notre API :
| Code | Sens selon MDN | Quand notre API le renvoie |
|---|---|---|
| 201 Created | Une nouvelle ressource a été créée | Après un POST réussi |
| 204 No Content | Pas de contenu à envoyer | Après une suppression |
| 400 Bad Request | Requête incompréhensible, syntaxe invalide | JSON mal formé |
| 401 Unauthorized | Le client doit s'authentifier | Jeton absent, invalide ou expiré |
| 404 Not Found | Ressource introuvable | Tâche inexistante ou d'un autre utilisateur |
| 409 Conflict | Conflit avec l'état du serveur | E-mail déjà inscrit |
| 413 Content Too Large | Corps de requête plus grand que la limite du serveur | Corps de plus de 10 ko |
| 415 Unsupported Media Type | Format de données non supporté | Corps absent ou qui n'est pas du JSON |
| 422 Unprocessable Content | Requête bien formée mais erreurs sémantiques | Données refusées par la validation |
| 429 Too Many Requests | Trop de requêtes en un temps donné | Trop de tentatives d'inscription ou de connexion |
| 500 Internal Server Error | Situation que le serveur ne sait pas traiter | Bug ou panne imprévue |
La fiche REST Security de l'OWASP tranche le piège classique : 401 quand l'authentification manque ou échoue, 403 quand elle a réussi mais que l'utilisateur n'a pas les droits. Pour la tâche d'un autre utilisateur, notre API répond 404, ce qui évite de confirmer qu'elle existe.
Pourquoi une API REST Node.js avec Express en 2026
Node.js reste la technologie web la plus citée par les développeurs, et Express le framework serveur le plus utilisé de cet écosystème. Dans l'enquête Stack Overflow 2025, 48,7 % des 23 678 répondants à la question sur les technologies web déclarent avoir beaucoup travaillé avec Node.js sur l'année écoulée, et 19,9 % avec Express. NestJS est à 6,7 % et Fastify à 2,9 %.
L'édition 2026, menée du 23 juin au 5 août 2026 selon sa méthodologie, ne publie pas de classement des frameworks web : ces chiffres de 2025 restent les plus récents de cette source. Elle confirme que JavaScript est le langage le plus utilisé, par 62,0 % des 14 099 répondants à la question (Stack Overflow 2026).
Express n'est ni le plus rapide ni le plus structurant, mais sa simplicité en fait un bon outil pour apprendre : un routeur, des middlewares, rien d'imposé. Côté Python, l'équivalent serait Flask ou FastAPI, présentés dans notre guide pour apprendre Python en 2026.
Étape 1 : installer Node.js et initialiser le projet
Installez une version LTS : la page des versions de Node.js réserve la production aux versions LTS, actives ou en maintenance.
| Composant | Version au 24 octobre 2026 | Statut |
|---|---|---|
| Node.js 24 « Krypton » | 24.22.1, publiée le 20 octobre 2026 | LTS, en maintenance depuis le 20 octobre, fin prévue le 30 avril 2028 |
| Node.js 26 | 26.12.1, publiée le 21 octobre 2026 | Current, passage en LTS prévu le 28 octobre 2026 |
| Node.js 22 « Jod » | 22.23.4 | LTS de maintenance, fin prévue le 30 avril 2027 |
| Node.js 20 | 20.20.2 | Fin de vie |
| Express 5 | 5.3.0, publiée le 9 octobre 2026 | Version par défaut sur npm, Node.js 18 minimum |
Sources : le calendrier officiel des versions, les notes de version d'Express et sa page de support. À partir de Node.js 27, chaque version majeure deviendra LTS, sur un cycle annuel. Créez ensuite le projet :
node --version mkdir api-taches && cd api-taches npm init -y npm install express zod jsonwebtoken helmet rate-limiter-flexible Au 24 octobre 2026, le registre npm sert Express 5.3.0, Zod 4.6.7, jsonwebtoken 9.0.3, Helmet 8.3.0 et rate-limiter-flexible 11.2.3. Complétez package.json ainsi :
{ "name": "api-taches", "version": "1.0.0", "type": "module", "engines": { "node": ">=24" }, "scripts": { "dev": "node --watch --env-file=.env src/server.js", "start": "node --env-file-if-exists=.env src/server.js", "test": "node --test" }, "dependencies": { "express": "^5.3.0", "helmet": "^8.3.0", "jsonwebtoken": "^9.0.3", "rate-limiter-flexible": "^11.2.3", "zod": "^4.6.7" } } Plus besoin de nodemon ni de dotenv : selon la documentation de la ligne de commande, --watch est stable depuis Node.js 22, et --env-file comme --env-file-if-exists ne sont plus expérimentaux depuis Node.js 24.10 (journal des modifications de la 24.10.0). Le script start charge .env s'il existe, sans échouer en production où les variables sont injectées par l'hébergeur. Créez enfin un fichier .env, exclu de Git :
PORT=3000 DB_PATH=data.db JWT_SECRET=collez-ici-une-valeur-aleatoire Pour générer le secret, node -e "console.log(require('node:crypto').randomBytes(32).toString('hex'))" produit 256 bits aléatoires, la taille minimale qu'impose la RFC 7518, section 3.2 pour une clé HS256 ; sans cette valeur, server.js refuse de démarrer. Pour l'éditeur, voyez notre sélection d'extensions VS Code.
Étape 2 : créer le serveur Express 5
L'application et son démarrage vivent dans deux fichiers, pour que les tests importent l'application sans ouvrir de port. L'arborescence finale :
api-taches/ package.json .env src/ app.js application Express server.js démarrage db.js base SQLite schemas.js schémas Zod motdepasse.js hachage scrypt middleware/ validation, auth, limiteur, erreurs routes/ auth.js, taches.js test/ api.test.js Le fichier src/app.js assemble les middlewares et les routeurs :
import express from 'express'; import helmet from 'helmet'; import { routerAuth } from './routes/auth.js'; import { routerTaches } from './routes/taches.js'; import { routeInconnue, gestionErreurs } from './middleware/erreurs.js'; export const app = express(); if (process.env.TRUST_PROXY) { app.set('trust proxy', Number(process.env.TRUST_PROXY)); } app.use(helmet()); app.use(express.json({ limit: '10kb' })); app.get('/health', (req, res) => { res.json({ statut: 'ok' }); }); app.use('/api/v1/auth', routerAuth); app.use('/api/v1/taches', routerTaches); app.use(routeInconnue); app.use(gestionErreurs); Helmet ajoute des en-têtes de sécurité et retire X-Powered-By, comme le recommande la page sécurité en production d'Express. express.json() limite le corps à 100 ko par défaut selon la référence de l'API Express 5 : 10 ko suffisent pour une tâche. Le préfixe /api/v1 permettra une version 2 sans casser les clients. Derrière un proxy inverse, TRUST_PROXY=1 indique à Express de lire l'adresse du client dans X-Forwarded-For à un saut de distance, comme le décrit la page Express derrière un proxy ; sans proxy, laissez la variable vide, sinon un client pourrait falsifier son adresse.
Le fichier src/server.js démarre le serveur :
import { app } from './app.js'; if (Buffer.byteLength(process.env.JWT_SECRET ?? '') < 32) { throw new Error('JWT_SECRET doit contenir au moins 32 octets (256 bits)'); } const port = Number(process.env.PORT ?? 3000); app.listen(port, (error) => { if (error) throw error; console.log(`API à l'écoute sur http://localhost:${port}`); }); Le paramètre error est une nouveauté d'Express 5 : selon le guide de migration, app.listen appelle ce rappel en cas d'erreur du serveur, comme un port déjà occupé.
Étape 3 : stocker les données dans SQLite
Node.js embarque SQLite depuis la version 22.5, dans le module node:sqlite, sans option à activer depuis les versions 22.13 et 23.4. La documentation de Node.js 24 le classe « release candidate » (stabilité 1.2) depuis la version 24.15, publiée le 15 avril 2026 (journal des modifications de la 24.15.0). Pour un tutoriel, c'est le plus simple : zéro dépendance, zéro serveur.
import { DatabaseSync } from 'node:sqlite'; export const db = new DatabaseSync(process.env.DB_PATH ?? 'data.db'); db.exec(` CREATE TABLE IF NOT EXISTS utilisateurs ( id INTEGER PRIMARY KEY, email TEXT NOT NULL UNIQUE, hash TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS taches ( id INTEGER PRIMARY KEY, titre TEXT NOT NULL, terminee INTEGER NOT NULL DEFAULT 0, proprietaire INTEGER NOT NULL REFERENCES utilisateurs(id) ); `); Les clés étrangères sont actives par défaut, selon la même page. Toutes les requêtes passent par db.prepare() avec des paramètres ? : les valeurs sont liées à part du texte SQL, ce qui bloque l'injection SQL.
Deux limites. Sous Node.js 26, la classe s'appelle Database et DatabaseSync devient un alias déprécié dans la documentation seulement (DEP0210). Surtout, l'API est synchrone : chaque requête bloque la boucle d'événements, alors que la page performances en production d'Express déconseille les fonctions synchrones.
Pour une API REST Node.js en production avec du trafic concurrent, préférez PostgreSQL, la base la plus utilisée : 57,9 % des 12 061 répondants concernés de l'enquête Stack Overflow 2026, devant SQLite (42,1 %) et MySQL (35,1 %) ; MongoDB est à 19,0 %. Il faudra un pilote asynchrone et des await, mais les routes gardent la même forme.
Étape 4 : écrire les routes et les verbes HTTP
Les routes traduisent le tableau des verbes en code. Toutes passent par exigerAuth : chaque utilisateur ne voit que ses tâches. Les middlewares utilisés sont écrits aux étapes 5 et 7. Voici src/routes/taches.js :
import { Router } from 'express'; import { db } from '../db.js'; import { exigerAuth } from '../middleware/auth.js'; import { exigerJson, valider } from '../middleware/validation.js'; import { schemaCreation, schemaRemplacement, schemaModification } from '../schemas.js'; export const routerTaches = Router(); routerTaches.use(exigerAuth); const versJson = (t) => ({ id: t.id, titre: t.titre, terminee: t.terminee === 1 }); const introuvable = (res) => res.status(404).json({ erreur: 'Tâche introuvable' }); function lireId(req) { const id = Number(req.params.id); return Number.isSafeInteger(id) && id > 0 ? id : null; } function trouver(id, proprietaire) { return db .prepare('SELECT id, titre, terminee FROM taches WHERE id = ? AND proprietaire = ?') .get(id, proprietaire); } routerTaches.get('/', (req, res) => { const taches = db .prepare('SELECT id, titre, terminee FROM taches WHERE proprietaire = ? ORDER BY id') .all(req.utilisateurId); res.json(taches.map(versJson)); }); routerTaches.get('/:id', (req, res) => { const id = lireId(req); const tache = id && trouver(id, req.utilisateurId); if (!tache) return introuvable(res); res.json(versJson(tache)); }); routerTaches.post('/', exigerJson, valider(schemaCreation), (req, res) => { const { titre, terminee = false } = req.donnees; const { lastInsertRowid } = db .prepare('INSERT INTO taches (titre, terminee, proprietaire) VALUES (?, ?, ?)') .run(titre, terminee ? 1 : 0, req.utilisateurId); const id = Number(lastInsertRowid); res.status(201).location(`${req.baseUrl}/${id}`).json({ id, titre, terminee }); }); function mettreAJour(req, res) { const id = lireId(req); const actuelle = id && trouver(id, req.utilisateurId); if (!actuelle) return introuvable(res); const tache = { ...versJson(actuelle), ...req.donnees }; db.prepare('UPDATE taches SET titre = ?, terminee = ? WHERE id = ?') .run(tache.titre, tache.terminee ? 1 : 0, id); res.json(tache); } routerTaches.put('/:id', exigerJson, valider(schemaRemplacement), mettreAJour); routerTaches.patch('/:id', exigerJson, valider(schemaModification), mettreAJour); routerTaches.delete('/:id', (req, res) => { const id = lireId(req); if (!id) return introuvable(res); const { changes } = db .prepare('DELETE FROM taches WHERE id = ? AND proprietaire = ?') .run(id, req.utilisateurId); if (Number(changes) === 0) return introuvable(res); res.sendStatus(204); }); Le guide de migration vers Express 5 retire res.json(objet, 201) au profit de res.status(201).json(objet), et res.send(204) au profit de res.sendStatus(204). L'en-tête Location du POST donne au client l'URL de la ressource créée.
La syntaxe des chemins a aussi changé : un joker doit être nommé (/*splat au lieu de /*) et un paramètre optionnel s'écrit entre accolades. Pour migrer un projet Express 4, le guide propose npx codemod@latest @expressjs/v5-migration-recipe.
Étape 5 : valider les données avec Zod
Ne faites jamais confiance au corps d'une requête. La fiche REST de l'OWASP demande de valider longueur, plage, format et type de chaque entrée, et de refuser en 415 un Content-Type inattendu. Les schémas vont dans src/schemas.js :
import * as z from 'zod'; const titre = z.string().trim().min(1).max(200); export const schemaIdentifiants = z.strictObject({ email: z.string().trim().toLowerCase().pipe(z.email()), motDePasse: z.string().min(15).max(128), }); export const schemaCreation = z.strictObject({ titre, terminee: z.boolean().optional() }); export const schemaRemplacement = z.strictObject({ titre, terminee: z.boolean() }); export const schemaModification = schemaRemplacement.partial(); z.strictObject refuse les clés inconnues : impossible d'injecter un champ proprietaire. L'e-mail est nettoyé et mis en minuscules avant d'être validé par z.email(), la syntaxe de Zod 4 (référence de l'API Zod), pour qu'Ada@Example.com et ada@example.com désignent le même compte. Le minimum de 15 caractères suit la fiche Authentication de l'OWASP et la norme NIST SP 800-63B, révision 4 (section 3.1.1.2) pour un mot de passe utilisé sans second facteur ; le maximum doit rester d'au moins 64 caractères. Le middleware src/middleware/validation.js range le résultat nettoyé dans req.donnees :
import * as z from 'zod'; export function exigerJson(req, res, next) { if (!req.is('application/json')) { return res.status(415).json({ erreur: 'Le corps doit être en application/json' }); } next(); } export function valider(schema) { return (req, res...
