Express est populaire parce qu'il est léger et flexible. Cette même flexibilité signifie que de petites erreurs de configuration ou des chemins de code peuvent produire des comportements difficiles à expliquer sous charge ou après un changement en apparence anodin. Ce guide vous donne un chemin pratique pour isoler rapidement les problèmes, vérifier les correctifs et récupérer en toute sécurité.
Il se concentre sur ce que vous pouvez observer et modifier avec un risque minimal : versions, variables d'environnement, utilisation des ports, journaux, analyse JSON, CORS, gestion des erreurs et défaillances typiques de la couche de données (par exemple, MongoDB). Chaque étape inclut des commandes ou des exemples de configuration uniquement là où ils s'appliquent directement.
Les exemples construits sont clairement identifiés comme tels. Remplacez les ports d'exemple, les noms d'environnement et les chemins par les vôtres.
## Introduction
Le dépannage fonctionne mieux lorsqu'il est visible et délimité. Commencez par inventorier ce qui tourne réellement, puis adoptez une configuration minimale et sûre pour éliminer les angles morts (journalisation, gestion des erreurs, points de terminaison de santé). Vérifiez les bases localement avant d'élargir l'investigation. Gardez les étapes de récupération réversibles et faciles à valider.
## Inventaire des versions et de l'environnement
Avant de changer quoi que ce soit, enregistrez exactement ce qui tourne. Cela évite de discuter avec des fantômes causés par des versions mal assorties ou des variables d'environnement.
### 1. Enregistrer les versions d'exécution
- Version de Node.js :
node -v
- Version de npm :
npm -v
- Version locale d'Express utilisée par le projet :
npm list express
Si le résultat montre plusieurs versions dans un monorepo, enregistrez celle sous le chemin du service API.
### 2. Capturer l'OS et l'utilisation des ports
- Utilisateur courant et bases de l'OS :
uname -a # macOS/Linux
ver # Windows Command Prompt
- Occupation du port pour un port Express typique (remplacez 3000 par votre port) :
lsof -i :3000 -P -n # macOS/Linux
netstat -anp | grep :3000 # Alternative Linux
netstat -ano | findstr :3000 # Windows
Si un autre processus est lié au port, vous verrez son PID et pourrez décider de l'arrêter ou d'utiliser un port différent pour l'API.
### 3. Instantané des variables d'environnement affectant l'API
Les variables typiques incluent PORT , NODE_ENV et les paramètres de connexion à la base de données.
# macOS/Linux
printenv | grep -E '^(PORT|NODE_ENV|MONGODB_|DB_|JWT_|API_)'
# Windows PowerShell
Get-ChildItem Env: | Where-Object { $_.Name -match '^(PORT|NODE_ENV|MONGODB_|DB_|JWT_|API_)' }
Enregistrez la sortie dans vos notes d'incident. De petites différences, comme un PORT manquant ou un NODE_ENV différent, expliquent bien des surprises.
### 4. Inspecter package.json et la commande de démarrage
Ouvrez package.json et notez :
- scripts.start et scripts.dev
- engines (si présent)
- express , body-parser (le cas échéant), cors , bibliothèques de journalisation ( morgan , pino , winston )
Ces éléments déterminent comment le service démarre réellement et quels middlewares sont en jeu.
## Chemin de configuration sûr
Adoptez de petits changements réversibles qui améliorent l'observabilité et la sécurité sans modifier la logique métier.
### 1. Journalisation minimale et gestion des erreurs
Ajoutez la journalisation des requêtes avec morgan (conviviale pour le développement) et un gestionnaire d'erreurs centralisé. Cet exemple est construit.
// app.js (exemple construit)
const express = require('express');
const morgan = require('morgan');
const cors = require('cors');
const app = express();
// Analyse JSON avec une limite raisonnable ; faire remonter les erreurs de syntaxe comme 400
app.use(express.json({ limit: '1mb' }));
app.use((err, req, res, next) => {
if (err && err.type === 'entity.parse.failed') {
return res.status(400).json({ error: 'Corps JSON invalide' });
}
return next(err);
});
// Journalisation des requêtes
app.use(morgan('combined'));
// CORS : commencer étroit et explicite
app.use(cors({
origin: ['https://votre-frontend.example.com'],
methods: ['GET','POST','PUT','DELETE','OPTIONS'],
credentials: true
}));
// Points de terminaison de santé et d'information
app.get('/healthz', (req, res) => res.status(200).json({ status: 'ok' }));
app.get('/info', (req, res) => res.json({ name: 'example-api', version: process.env.BUILD_SHA || 'dev' }));
// Route d'exemple
app.get('/api/v1/ping', (req, res) => res.json({ pong: true }));
// Gestionnaire d'erreurs centralisé
app.use((err, req, res, next) => {
console.error('Erreur non gérée', { message: err.message, stack: err.stack });
if (res.headersSent) return next(err);
res.status(500).json({ error: 'Erreur interne du serveur' });
});
module.exports = app;
### 2. Point d'entrée serveur clair avec temps d'attente
Définissez des temps d'attente au niveau serveur pour éviter les blocages et journalisez le démarrage. Ajustez les valeurs selon vos budgets de temps en amont.
// server.js (exemple construit)
const http = require('http');
const app = require('./app');
const PORT = Number(process.env.PORT || 3000);
const server = http.createServer(app);
// Éviter que les requêtes ne pendent indéfiniment
server.requestTimeout = 60_000; // 60s
server.headersTimeout = 65_000; // 65s
server.listen(PORT, () => {
console.log(`Serveur en écoute sur le port ${PORT} env=${process.env.NODE_ENV || 'development'}`);
});
### 3. Gestionnaires de processus gracieux
Ne masquez pas les problèmes ; journalisez et quittez sur les signaux fatals pour que votre gestionnaire de processus puisse redémarrer.
// index.js (exemple construit)
require('dotenv').config();
require('./server');
process.on('unhandledRejection', (reason) => {
console.error('Rejet non géré', reason);
process.exit(1);
});
process.on('uncaughtException', (err) => {
console.error('Exception non capturée', err);
process.exit(1);
});
### 4. Garder CORS explicite
Évitez les origines wildcard pendant le dépannage. Si vous devez autoriser plusieurs origines, listez-les explicitement pour voir laquelle échoue.
### 5. Points de terminaison de santé
Retournez du JSON simple et HTTP 200 seulement quand les dépendances de base sont accessibles. Par exemple, si la base de données est en panne, retournez 503 avec un corps clair.
// health.js (exemple construit)
app.get('/healthz', async (req, res) => {
const checks = { db: 'down' };
try {
// Pseudocode : ping votre BD ici
// await db.admin().command({ ping: 1 });
checks.db = 'up';
} catch (e) {
return res.status(503).json({ status: 'degraded', checks });
}
res.json({ status: 'ok', checks });
});
## Vérification et diagnostics
Avec les valeurs par défaut sûres en place, prouvez que les bases fonctionnent et rassemblez des signaux pour le débogage ultérieur.
### 1. Démarrer le service et confirmer le port
node index.js
# Attendu : Serveur en écoute sur le port 3000 env=development
Si rien ne s'affiche, confirmez que vous lancez le fichier attendu et que console.log n'a pas été supprimé.
### 2. Vérifications de base des points de terminaison
- Santé :
curl -i http://localhost:3000/healthz
# Attendu : HTTP/1.1 200 OK et un petit corps JSON
- Info :
curl -i http://localhost:3000/info
# Attendu : 200 avec champs name et version
- Route d'exemple :
curl -i http://localhost:3000/api/v1/ping
# Attendu : 200 avec {"pong":true}
### 3. Vérification de l'analyse JSON
- JSON valide :
curl -i -X POST http://localhost:3000/api/v1/echo \
-H 'Content-Type: application/json' \
-d '{"hello":"world"}'
# Attendu : 200 avec JSON écho (exemple de route construite)
- JSON invalide (exemple construit) :
curl -i -X POST http://localhost:3000/api/v1/echo \
-H 'Content-Type: application/json' \
-d '{"hello":"world"}' # accolade fermante manquante
# Attendu : 400 avec {"error":"Corps JSON invalide"}
Si vous voyez une pile d'appels au lieu d'un 400, confirmez que votre middleware d'erreur d'analyse JSON s'exécute avant les autres gestionnaires.
### 4. Vérification CORS (depuis la ligne de commande)
curl -I http://localhost:3000/api/v1/ping \
-H 'Origin: https://votre-frontend.example.com'
# Attendu : Access-Control-Allow-Origin: https://votre-frontend.example.com
Si l'en-tête est absent, révisez la configuration cors() et confirmez que la route est sous app.use(cors(...)) ou que vous appliquez CORS au niveau du routeur.
### 5. Vérifications de port et de processus
Si le lien échoue avec EADDRINUSE , trouvez le processus utilisant le port et décidez de l'arrêter ou de changer PORT .
lsof -i :3000 -P -n # macOS/Linux
netstat -ano | findstr :3000 # Windows
### 6. Vérification de la connectivité à la base de données
Faites remonter la connectivité dans /healthz . Pour une vérification manuelle (exemple construit avec une commande de type Mongo) :
# Vérification hypothétique en shell, remplacez par votre outil de BD
mongo --eval 'db.adminCommand({ ping: 1 })'
# Attendu : ok: 1
Si vos journaux Express montrent ECONNREFUSED ou ETIMEDOUT vers la base de données, vérifiez hôte, port, identifiants et toute ACL réseau.
## Modes de défaillance et récupération
Le tableau ci-dessous associe les symptômes courants d'API Express aux causes probables et aux premières actions sûres.
| Symptôme ou extrait de journal | Cause probable | Premières vérifications | Correctif sûr |
| EADDRINUSE :3000 au démarrage | Port déjà utilisé | lsof/netstat pour le port 3000 | Changer PORT ou arrêter le processus en conflit |
| ECONNREFUSED vers hôte BD | Base de données en panne ou mauvais hôte/port | Ping hôte et port BD | Corriger la config ou redémarrer la BD ; retourner 503 dans healthz |
| Unexpected token in JSON at position X | JSON client mal formé | Inspecter le corps de requête et Content-Type | Intercepter l'erreur d'analyse, retourner 400 |
| Cannot set headers after they are sent | Double envoi de réponse | Chercher plusieurs res.send/res.json/res.end | Assurer un seul chemin de réponse ; ajouter des return |
| socket hang up ou ETIMEDOUT | Temps d'attente amont ou serveur | Vérifier server.requestTimeout ; latence dans les journaux | Ajuster les timeouts ; améliorer la performance des gestionnaires |
| 404 pour route connue | Mauvaise chemin de montage ou préfixe | Confirmer app.use('/api', router) vs requêtes | Aligner route et chemin client |
| Erreur CORS dans la console navigateur | Origine non autorisée | Inspecter Access-Control-Allow-Origin | Ajouter origine explicite ou ajuster la config CORS |
### Scénario A : Conflit de port (EADDRINUSE)
- Confirmer le conflit
lsof -i :3000 -P -n
- Containment
- Si l'autre processus est le vôtre (par exemple, un vieux serveur de dev sur le port 3000), arrêtez-le avec sa commande d'arrêt normale.
- S'il n'est pas le vôtre, ne le tuez pas aveuglément. Changez le PORT de votre API pour un port libre et continuez le dépannage.
- Correctif sûr
Définissez PORT sur une valeur libre et redémarrez :
PORT=3001 node index.js
- Vérification
Accédez à http://localhost:3001/healthz et confirmez 200.
- Rollback
Remettez PORT à l'original une fois que le processus en conflit n'utilise plus le port.
### Scénario B : Plantages sur rejets de promesses non gérés
Symptômes : le processus quitte inopinément, les journaux montrent UnhandledPromiseRejectionWarning ou similaire.
- Containment
- Ajoutez un gestionnaire unhandledRejection de haut niveau qui journalise et quitte (montré plus haut). Quitter évite qu'un processus corrompu continue de servir du trafic.
- Cause racine
- Trouvez les gestionnaires async qui lèvent sans passer les erreurs à next(err) ou sans try/catch .
Exemple de correctif construit :
// Avant
app.get('/api/v1/user', async (req, res) => {
const user = await loadUser(req.query.id); // lève -> chemin de plantage
res.json(user);
});
// Après
app.get('/api/v1/user', async (req, res, next) => {
try {
const user = await loadUser(req.query.id);
res.json(user);
} catch (err) {
next(err);
}
});
- Vérification
Reproduisez l'appel échoué et attendez un 500 avec la réponse d'erreur centralisée, pas un plantage.
- Rollback
Si un changement récent a introduit le problème, revenez au dernier commit connu bon du fichier de gestionnaire de route.
### Scénario C : Échecs d'analyse JSON inondant les journaux
Symptômes : 500 et piles d'appels pour corps JSON invalides.
- Containment
- Assurez-vous que le middleware express.json est installé avant les routes et incluez le gestionnaire d'erreur d'analyse du Chemin de configuration sûr.
- Cause racine
- Clients envoyant du JSON mal formé ou mauvais Content-Type .
- Correctif sûr
- Retournez 400 pour les erreurs d'analyse avec un message court.
- Optionnellement, renforcez la vérification Content-Type :
app.use((req, res, next) => {
if (['POST','PUT','PATCH'].includes(req.method)) {
if (!req.is('application/json')) {
return res.status(415).json({ error: 'application/json attendu' });
}
}
next();
});
- Vérification
Envoyez des requêtes JSON valides et invalides. Attendez 200 pour valide, 400 ou 415 pour invalide.
- Rollback
Si les clients dépendent d'un comportement plus souple, retirez temporairement la vérification Content-Type mais gardez le gestionnaire d'erreur d'analyse.
### Scénario D : Panne ou mauvaise configuration de la base de données
Symptômes : erreurs 500 sur routes appuyées sur la BD ; point de terminaison de santé montre dégradé.
- Containment
- Court-circuitez les requêtes quand la BD est en panne pour éviter les longs timeouts. Retournez 503 avec Retry-After .
Exemple de garde construit :
let dbIsUp = false;
// Mettre à jour dbIsUp à intervalle régulier ou sur événements de connexion
setInterval(async () => {
try {
// await db.admin().command({ ping: 1 });
dbIsUp = true;
} catch {
dbIsUp = false;
}
}, 5000);
app.use((req, res, next) => {
if (!dbIsUp && req.path.startsWith('/api/v1/orders')) {
res.set('Retry-After', '30');
return res.status(503).json({ error: 'BD indisponible' });
}
next();
});
- Cause racine
- Mauvais hôte/port, identifiants tournés, règles pare-feu, ou processus BD planté.
- Correctif sûr
- Corrigez la chaîne de connexion et les identifiants.
- Ajoutez un timeout de connexion et une politique de réessai limitée pour que votre appli échoue vite au lieu de pendre :
// Pseudocode : lors de la création du client BD
const client = new DBClient({ connectTimeoutMS: 5000 });
- Vérification
- healthz doit passer de 503 à 200 dans un ou deux intervalles après la récupération de la BD.
- Les routes métier doivent répondre dans les budgets de latence normaux.
- Rollback
Si une mise à jour de schéma ou de pilote a causé le problème, revenez à la version et configuration du paquet dernier connu bon.
### Scénario E : Utilisation mémoire ou CPU élevée au fil du temps
Symptômes : latence augmente, redémarrages de processus, ou erreurs mémoire insuffisante.
- Containment
- Réduisez le trafic si possible, ou échellez temporairement avec plusieurs instances derrière un équilibreur de charge si disponible. Sinon, redémarrez le processus pendant une fenêtre de maintenance pour restaurer le service pendant que vous enquêtez.
- Cause racine
- Fuites depuis tableaux globaux, setInterval sans clearInterval , ou attachement répété d'écouteurs d'événements.
Exemple construit d'une fuite et son correctif :
// Fuyant : pousse dans un tableau global à chaque requête
const cache = [];
app.get('/api/v1/items', (req, res) => {
cache.push(Date.now());
res.json({ ok: true });
});
// Corrigé : utiliser un cache borné
const cache2 = [];
app.get('/api/v1/items', (req, res) => {
if (cache2.length > 1000) cache2.shift();
cache2.push(Date.now());
res.json({ ok: true });
});
- Diagnostics sûrs
- Journalisez la mémoire chaque minute pendant l'enquête :
setInterval(() => {
const m = process.memoryUsage();
console.log(`rss=${m.rss} heapUsed=${m.heapUsed}`);
}, 60_000);
- Vérification
Après le correctif, heapUsed doit se stabiliser pendant une période de charge stable.
- Rollback
Revenez sur la fonctionnalité soupçonnée d'introduire la fuite ; confirmez que la mémoire revient aux valeurs de base (exemple hypothétique : rss ~ 120MB sous charge stable).
## Liste de contrôle opérationnelle
Utilisez cette liste pendant le développement et les incidents. Gardez-la courte et objective.
| Tâche | Quand | Comment vérifier |
| Enregistrer versions Node, npm, Express | Début du travail ou incident | node -v, npm -v, npm list express |
| Confirmer liaison au port | Au démarrage ou après EADDRINUSE | lsof/netstat montre un seul écouteur |
| Vérifier points de terminaison santé et info | Après chaque déploiement ou correctif | /healthz retourne 200 quand dépendances sont up |
| Vérifier gestion erreurs JSON | Après changements config analyseur | JSON invalide retourne 400, pas 500 |
| Valider CORS | Quand origine front-end change | Réponse a bon Access-Control-Allow-Origin |
| Observer timeouts | Après ajout logique lente | Requêtes longues finissent dans requestTimeout |
| Tester connectivité BD | Avant activation routes lourdes BD | healthz rapporte db: up |
| Surveiller mémoire et CPU | Pendant l'enquête | rss et heapUsed stables dans le temps |
## Résultats attendus et ce qui ressemble à du bon travail
- Le démarrage imprime une ligne claire avec port et environnement, et le processus reste stable sous trafic léger.
- /healthz retourne 200 seulement quand les dépendances de base sont up ; sinon, 503 avec une explication simple.
- Corps JSON invalides donnent 400 avec message concis ; corps valides réussissent.
- Réponses CORS incluent l'origine exacte autorisée pour les routes pertinentes.
- Timeouts sont finis, visibles dans les journaux, et alignés avec vos limites amont.
## Workflow de vérification de défaillance et de récupération
Utilisez cette séquence répétable :
- Identifier
- Capturez le message d'erreur exact et l'horodatage des journaux.
- Notez la route, la méthode, et tout ID de corrélation ou de requête.
- Contenir
- Préférez les bascules de configuration et clauses de garde (retourner 503) aux réécritures de code pendant que vous apprenez.
- Si une seule route échoue, isolez-la derrière un feature flag ou une garde au niveau route.
- Diagnostiquer
- Reproduisez localement avec curl en utilisant la même méthode, en-têtes et corps.
- Comparez le comportement entre environnements en vérifiant les différences NODE_ENV et PORT .
- Corriger
- Appliquez le plus petit changement qui adresse la cause racine (par exemple, ajouter try/catch , ajuster origine CORS, corriger hôte BD).
- Vérifier
- Confirmez avec un cas de succès et un cas d'échec représentatif.
- Surveillez les journaux 5-10 minutes pour assurer l'absence d'erreurs récurrentes (fenêtre d'observation hypothétique).
- Rollback si nécessaire
- Si le changement augmente les taux d'erreur, revenez à la dernière version connue bonne du fichier ou configuration spécifique.
## Conseils pratiques
- Gardez les messages d'erreur brefs pour les clients mais journalisez les détails complets côté serveur.
- Préférez les listes explicites (origines CORS, routes) aux wildcards pendant l'enquête.
- Définissez des limites JSON et tailles de corps conservatrices, puis assouplissez au besoin avec des preuves.
- En cas de doute sur les erreurs async, enveloppez les gestionnaires et appelez toujours next(err) en cas d'échec.
- Encodez les budgets de temps : requestTimeout et timeouts client amont doivent être cohérents.
## Conclusion
Un dépannage Express solide commence par la visibilité : connaissez vos versions, ports et variables d'environnement, et ajoutez une journalisation et gestion d'erreurs minimales. Vérifiez les routes de base, l'analyse JSON, CORS et les vérifications de santé avant de plonger dans des changements plus profonds. Les défaillances courantes comme les conflits de port, rejets non gérés, erreurs d'analyse et pannes de base de données ont toutes des premières étapes sûres et des signaux de vérification clairs.
Commencez par un pilote étroit et mesurable localement, puis étendez vos vérifications. Utilisez la liste de contrôle pour garder chaque incident ciblé et répétable. Avec ces pratiques en place, vous passerez moins de temps à deviner et plus de temps à livrer une API stable.