Introduction
Express est l’un des frameworks web les plus utilisés pour Node.js, apprécié pour sa simplicité et sa flexibilité. Cependant, cette flexibilité peut entraîner des erreurs subtiles difficiles à diagnostiquer en production. Ce guide se concentre sur les erreurs les plus courantes rencontrées lors de la création et de la maintenance d’API Express, avec des exemples pratiques, des correctifs concrets et des étapes de vérification.
L’objectif est la sécurité opérationnelle : observer avant de modifier, limiter le rayon d’impact, utiliser des variables fictives plutôt que des secrets, vérifier le résultat et documenter la récupération si l’état attendu n’est pas atteint. Chaque section comprend un scénario réel, la cause probable, une correction étape par étape et la confirmation de la résolution.
Inventaire des versions et de l’environnement
Avant de dépanner une erreur d’API Express, établissez une image claire de l’environnement. Utilisez les commandes suivantes pour recueillir les informations de version et l’état actuel :
node --version
npm --version
npx express --version
npm list express
Exemple de sortie attendue :
v20.11.0
10.2.4
4.18.2
[email protected]
La topologie de déploiement est importante : utilisez-vous un proxy inverse comme Nginx, un gestionnaire de processus comme PM2, ou un environnement sans serveur ? Chacun affecte la façon dont les erreurs se manifestent et l’accès aux journaux. Par exemple, derrière Nginx, les adresses IP des clients ne sont pas directement disponibles dans Express tant que vous n’avez pas défini app.set('trust proxy', true).
Exemple pratique : version d’Express incohérente
Une équipe a mis à niveau vers Express 5 dans une branche, mais le package.json de production référençait encore Express 4. Les routes utilisant app.route() fonctionnaient, mais la syntaxe des routes génériques avait changé, provoquant des 404.
Correctif :
npm install [email protected] --save
Vérifiez avec :
npm list express
La sortie attendue montre la version correcte. Sinon, réinstallez et vérifiez les conflits dans package-lock.json.
Chemin de configuration sûr
Une mauvaise configuration est une source fréquente d’erreurs d’exécution. Suivez un chemin de configuration sûr : identifiez le paramètre concerné, comprenez sa valeur par défaut et ses valeurs autorisées, modifiez un élément à la fois et vérifiez avant de continuer.
Pièges de configuration courants
1. Paramètre trust proxy manquant
Si votre application Express se trouve derrière un proxy inverse et que vous utilisez req.ip ou une limitation de débit basée sur l’IP, vous devez faire confiance au proxy.
app.set('trust proxy', 'loopback'); // ou une IP/sous-réseau spécifique
Sans cela, req.ip renvoie l’IP du proxy, ce qui casse les fonctionnalités basées sur l’IP.
Vérification :
Ajoutez une route temporaire :
app.get('/debug-ip', (req, res) => res.json({ ip: req.ip }));
Effectuez une requête depuis un client connu et confirmez que l’IP correspond au client, pas au proxy.
2. Limites de taille de l’analyseur de corps
Les gros payloads JSON peuvent échouer avec 413 Payload Too Large si la limite par défaut (100 Ko) est dépassée.
app.use(express.json({ limit: '2mb' }));
Définissez la limite selon les besoins de votre API, mais évitez les valeurs extrêmement élevées pour prévenir l’épuisement de la mémoire.
3. Variables d’environnement non chargées
Si process.env.PORT est indéfini, l’application peut se replier sur le port 3000, ou échouer au démarrage si le code l’exige. Utilisez dotenv et vérifiez tôt :
require('dotenv').config();
if (!process.env.PORT) {
console.error('PORT is not defined');
process.exit(1);
}
Vérification et récupération : Après chaque modification, redémarrez le serveur et vérifiez qu’il démarre sans avertissement, puis testez l’endpoint concerné.
Vérification et diagnostics
Diagnostiquer les erreurs nécessite d’observer le système sans introduire de nouveaux changements. Utilisez des commandes en lecture seule et une journalisation structurée pour capturer l’état.
Middleware d’erreur pour les diagnostics
Ajoutez un middleware temporaire de journalisation des erreurs (ou utilisez une bibliothèque comme morgan combinée à winston) :
app.use((err, req, res, next) => {
console.error('Timestamp:', new Date().toISOString());
console.error('Request:', req.method, req.originalUrl);
console.error('Error:', err.stack || err);
res.status(500).json({ error: 'Internal Server Error' });
});
Cela révèle la pile d’appels, les détails de la requête et l’heure, aidant à identifier l’endpoint défaillant.
Diagnostiquer une requête suspendue
Si les requêtes restent suspendues sans réponse, une opération asynchrone est souvent mal gérée. Utilisez curl avec un délai d’attente pour tester :
curl -m 10 -i http://localhost:3000/api/users
Si aucune réponse dans les 10 secondes, le serveur est bloqué. Vérifiez :
returnmanquant avantnext()dans un middleware.- Promesses non attendues ou non interceptées dans les gestionnaires de route asynchrones.
- Code synchrone bloquant (ex. : grand
JSON.parsedans une boucle).
Ajoutez des instructions console.log aux points clés ou utilisez le drapeau --inspect de Node pour attacher un débogueur.
Modes de défaillance et récupération
Comprendre les modes de défaillance courants aide à les prévenir. Voici les schémas d’échec les plus fréquents des API Express et comment s’en remettre.
1. Rejets de promesses non gérés dans les gestionnaires asynchrones
Symptôme : Express ne capture pas les erreurs levées dans les gestionnaires de route asynchrones par défaut (Express 4). La requête peut rester suspendue ou le processus peut planter avec UnhandledPromiseRejectionWarning.
Exemple d’erreur :
app.get('/users/:id', async (req, res) => {
const user = await db.getUser(req.params.id); // si cela rejette, Express ne capturera pas
res.json(user);
});
Correctif : Enveloppez dans try-catch ou utilisez un wrapper :
const asyncHandler = fn => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
app.get('/users/:id', asyncHandler(async (req, res) => {
const user = await db.getUser(req.params.id);
res.json(user);
}));
Vérification : Simulez une erreur de base de données (par exemple, arrêtez temporairement la base) et confirmez que la requête renvoie une réponse JSON 500 au lieu de rester suspendue.
Récupération : Si le processus a planté, redémarrez-le et appliquez le correctif. En production, utilisez un gestionnaire de processus comme PM2 pour le redémarrage automatique :
pm2 start app.js --name my-api --max-memory-restart 300M
2. Erreurs d’ordre des middlewares
Symptôme : Les requêtes contournent l’authentification ou renvoient des réponses inattendues car le middleware est mal ordonné.
Exemple :
app.use((req, res, next) => {
res.setHeader('X-Custom-Header', 'value');
next();
});
app.use(express.json()); // Si l’analyse du corps est après un middleware qui lit le corps, cela échoue
Correctif : Assurez-vous que express.json() est avant toute route nécessitant req.body, et placez les middlewares de gestion d’erreur en dernier (après toutes les routes).
Vérification : Envoyez une requête POST avec un corps JSON et vérifiez que req.body est rempli :
curl -X POST -H "Content-Type: application/json" -d '{"name":"test"}' http://localhost:3000/test
3. Mauvaise configuration du service de fichiers statiques
Symptôme : 404 pour les actifs statiques, ou le serveur expose des fichiers sensibles.
Correctif :
app.use(express.static('public', { index: false })); // désactiver le listing des répertoires
Utilisez un chemin absolu pour éviter toute confusion :
const path = require('path');
app.use('/static', express.static(path.join(__dirname, 'public')));
Vérification : Accédez à un fichier statique connu et confirmez 200, puis essayez la racine du répertoire et confirmez 404.
Pièges courants et comment les éviter
Au-delà des erreurs spécifiques, plusieurs habitudes causent des problèmes récurrents. Chacune inclut pourquoi elle se produit, comment l’éviter et les étapes de récupération.
Piège 1 : Bloquer la boucle d’événements
Pourquoi cela arrive : Les opérations gourmandes en CPU comme les lectures de fichiers synchrones, les grandes boucles ou les calculs complexes bloquent le thread unique de Node, stoppant toutes les requêtes.
Exemple :
app.get('/report', (req, res) => {
const data = fs.readFileSync('huge-file.csv', 'utf8'); // bloque
res.send(data);
});
Éviter : Utilisez les versions asynchrones (fs.readFile) ou déléguez à un worker thread.
Récupération : Si déjà bloqué, redémarrez le processus. Puis refactorisez en asynchrone. Surveillez le retard de la boucle d’événements avec process.hrtime() ou des outils comme clinic doctor.
Piège 2 : Configuration CORS incorrecte
Pourquoi cela arrive : Le serveur ne définit pas les en-têtes CORS, ou définit Access-Control-Allow-Origin: * sans comprendre les implications.
Correctif : Utilisez le middleware cors avec des origines explicites :
const cors = require('cors');
app.use(cors({ origin: 'https://yourfrontend.com' }));
Vérification :
curl -H "Origin: https://yourfrontend.com" -i http://localhost:3000/api
Vérifiez que la réponse inclut l’en-tête Access-Control-Allow-Origin approprié.
Piège 3 : Ne pas gérer correctement les 404
Pourquoi cela arrive : Les routes indéfinies renvoient un 404 HTML par défaut d’Express, ce qui peut casser les clients API qui attendent du JSON.
Correctif : Ajoutez une route fourre-tout après toutes les autres :
app.use((req, res, next) => {
res.status(404).json({ error: 'Not Found' });
});
Vérification : Requêtez un endpoint inexistant et confirmez la réponse JSON.
Liste de contrôle des opérations
Utilisez cette liste avant et après avoir apporté des modifications à une API Express. Chaque élément a un propriétaire désigné (exemple : responsable API) et doit être examiné chaque semaine ou à chaque déploiement.
| Élément | Propriétaire | Fréquence | Commande / étape de vérification |
|---|---|---|---|
| Vérifier les versions de Node et Express | Ingénieur DevOps | Hebdomadaire | node --version && npx express --version |
| Examiner les journaux d’erreurs pour les rejets non gérés | Développeur backend | Quotidien | pm2 logs --err --lines 100 ou équivalent |
| Tester tous les endpoints API avec une suite de tests de fumée | Ingénieur QA | Avant la sortie | npm test -- --grep smoke |
| Vérifier que les variables d’environnement sont correctement définies | Ingénieur DevOps | À chaque déploiement | node -e "require('dotenv').config(); console.log(process.env.NODE_ENV)" |
| Vérifier la configuration du proxy inverse (si utilisé) | Administrateur système | Mensuel | Inspecter la config Nginx : nginx -T |
| Confirmer que les paramètres CORS correspondent aux origines autorisées | Ingénieur sécurité | Trimestriel | Envoyer une requête OPTIONS avec l’en-tête Origin |
| Examiner les dépendances pour les vulnérabilités | Ingénieur sécurité | Hebdomadaire | npm audit |
| Surveiller l’utilisation de la mémoire et du CPU du processus | Ingénieur DevOps | Continu | pm2 monit ou métriques cloud |
Important : N’exécutez pas de commandes destructrices sans plan de sauvegarde ou de retour en arrière. Pour les changements de configuration, utilisez le contrôle de version et étiquetez les versions.
Conclusion
Les erreurs des API Express, bien que courantes, sont souvent évitables avec une configuration soignée, un ordre de middlewares approprié et une gestion robuste des erreurs. La clé est d’observer avant de changer, d’effectuer un changement à la fois et de vérifier le résultat avec des commandes ou des requêtes concrètes.
Commencez par l’inventaire de l’environnement, puis traitez la configuration, appliquez un middleware de diagnostic et comprenez les modes de défaillance. Utilisez la liste de contrôle pour maintenir la santé opérationnelle. N’oubliez pas : un flux de travail de dépannage fiable rend la défaillance visible, protège les valeurs sensibles, limite les modifications à la ressource prévue et définit la vérification de récupération avant qu’un incident ne force la décision.
Comme prochaine étape, choisissez une vérification à faible risque dans la liste de contrôle, enregistrez l’état actuel, exécutez la vérification, comparez le résultat avec le signal attendu et examinez les dépendances telles que Node.js, MongoDB (si utilisé) et la conception de l’API REST.