Liste de contrôle d’examen de code Node.js : toutes les étapes incluses

Node.js est passé d’une expérience audacieuse à l’épine dorsale du développement backend moderne. En 2025, 48,7 % des développeurs dans le monde utilisent Node.js, ce qui en fait le framework web le plus adopté sur la planète, selon le Stack Overflow Developer Survey. Des entreprises comme Netflix, PayPal et Amazon s’appuient sur lui pour leur infrastructure critique. L’écosystème npm contient désormais plus de 2 millions de packages. Cependant, cette popularité ne protège pas une base de code des problèmes qui s’installent lorsque personne ne la surveille d’assez près.

Malheureusement, un runtime rapide ne rend pas une application sûre ou maintenable. Il ne fait qu’accélérer la propagation des problèmes. C’est pourquoi une revue de code structurée est l’un des investissements les plus importants qu’une équipe Node.js puisse faire, que vous vous prépariez pour une levée de fonds, l’intégration d’une nouvelle équipe d’ingénierie, ou que vous lanciez un produit que vous prévoyez de supporter pendant les cinq prochaines années.

Chez Redwerk, nous développons et auditons des applications Node.js depuis des années, et cette checklist reflète ce que nos ingénieurs recherchent lors de chaque revue. Nous l’avons organisée de la même manière que nous exécutons nous-mêmes le processus.

Préparation avant la revue

Tout d’abord, commencer une revue sans préparation, c’est comme entrer dans une négociation sans avoir lu le brief. Cela signifie que vous pourrez peut-être atteindre votre objectif, mais vous manquerez les éléments les plus importants.

Définir la portée et les objectifs :

  • Identifiez d’abord si la revue couvre l’ensemble de la base de code ou si vous vous concentrerez sur un module spécifique, une couche d’API ou une version récente.
  • Fixez des critères de succès clairs. Cela signifie définir ce qui constitue une revue réussie. S’agit-il d’avoir moins de problèmes de sécurité, une meilleure couverture de tests ou des temps de réponse améliorés ?
  • Notez les zones problématiques connues signalées par l’équipe, telles que les points d’accès lents, les tests instables ou les dépendances récemment introduites.

Vérifier l’environnement et la configuration :

  • Confirmez que le projet démarre proprement en mode développement et production sans erreurs ni avertissements au lancement.
  • Vérifiez que la version de Node.js utilisée correspond à celle épinglée dans .nvmrc ou .node-version, et qu’il s’agit d’une version LTS active (24.x Active LTS (Krypton) du 22/04/2025 au 30/04/2028. 22.x Maintenance LTS (Jod) du 24/04/2024 au 30/04/2027 sont les recommandations actuelles pour la production).
  • Assurez-vous que `npm install` ou `yarn install` se termine sans avertissements de vulnérabilité ; exécutez `npm audit` et examinez les problèmes signalés avant de continuer.
  • Confirmez que les variables d’environnement sont chargées correctement et qu’aucun secret n’est codé en dur quelque part dans le projet.

Examiner la documentation et l’historique git :

  • Assurez-vous que le README décrit clairement comment configurer, exécuter, construire et tester le projet.
  • Examinez l’historique des commits récents pour comprendre ce qui a changé et pourquoi.
  • Vérifiez que les pull requests ouvertes sont rebasées sur la dernière branche principale afin d’éviter de passer en revue du code obsolète ou conflictuel.

Configurer les outils d’analyse statique :

  • Confirmez que ESLint utilise `eslint-plugin-n` pour couvrir les problématiques Node.js. Appliquez `no-process-exit` et assurez-vous que `no-sync` est strictement appliqué aux routes HTTP tout en l’autorisant pour les scripts de démarrage ou les outils CLI.
  • Confirmez qu’un formateur comme Prettier ou équivalent est en place et cohérent dans tout le projet.
  • Vérifiez que les vérifications de linting et de formatage s’exécutent automatiquement dans le pipeline CI/CD avant toute fusion.

Structure du projet et organisation du code

En regardant un projet Node.js bien organisé, vous pouvez discerner beaucoup de choses sur l’équipe qui l’a construit. Lorsque les modules sont dispersés, les responsabilités floues et les fichiers nommés par celui qui était pressé, chaque changement futur coûte deux fois plus cher qu’il ne devrait.

Structure des dossiers et limites des modules :

  • Vérifiez que le projet suit une structure cohérente, telle que la séparation des routes, des contrôleurs, des services et des couches d’accès aux données dans des dossiers distincts.
  • Confirmez que la logique métier ne réside pas dans les gestionnaires de routes ; les gestionnaires de routes doivent déléguer à des fonctions de service, et non exécuter des requêtes ou traiter des données directement.

Mauvaise pratique :

// Gestionnaire de route faisant trop de choses
app.post('/orders', async (req, res) => {
  const order = await db.query('INSERT INTO orders ...', [req.body]);
  await sendEmail(order.userEmail, 'Order confirmed');
  await updateInventory(order.items);
  res.json(order);
});

Bonne pratique :

// Gestionnaire de route déléguant à un service
app.post('/orders', async (req, res, next) => {
  try {
    const order = await OrderService.create(req.body);
    res.status(201).json(order);
  } catch (err) {
    next(err);
  }
});

Conventions de nommage et lisibilité :

  • Utilisez camelCase pour les variables et les fonctions, PascalCase pour les classes et UPPER_SNAKE_CASE pour les constantes.
  • Évitez les noms courts et cryptiques comme `fn`, `tmp` ou `data`. Chaque nom de fonction doit décrire ce qu’elle fait, pas ce qu’elle est.
  • Assurez-vous que les noms de fichiers sont cohérents. C’est-à-dire, si les contrôleurs sont nommés `user.controller.js`, ils doivent tous suivre ce modèle, sans passer à `user-controller.js` deux dossiers plus loin.

Utilisation des modules et CommonJS vs ESM :

  • Confirmez que le projet utilise un seul système de modules de manière cohérente, soit `require`/CommonJS, soit `import`/ESM ; le mélange des deux sans configuration est une source d’erreurs subtiles au moment de l’exécution.
  • Vérifiez que les modules internes sont importés à l’aide de chemins relatifs et qu’il n’existe pas de dépendances circulaires. Les dépendances circulaires se comportent différemment selon le système de modules : en CommonJS, elles produisent silencieusement `undefined` au moment de l’exécution, tandis qu’en ESM, elles déclenchent une `ReferenceError` fatale.

Modèles asynchrones et état de la boucle d’événements

C’est là que les projets Node.js divergent le plus des applications écrites dans d’autres langages. Node.js traite tout sur un seul thread via une boucle d’événements, et selon la documentation officielle de Node.js, chaque callback JavaScript doit se terminer rapidement. Bloquer cette boucle, même brièvement, oblige toutes les requêtes concurrentes à attendre.

Éviter de bloquer la boucle d’événements :

  • Recherchez les appels système de fichiers synchrones dans les chemins critiques : `fs.readFileSync`, `fs.writeFileSync`, et des fonctions similaires bloquent entièrement la boucle d’événements jusqu’à ce que l’opération soit terminée. Remplacez-les par leurs équivalents asynchrones.
  • Vérifiez les boucles de traitement de données synchrones volumineuses. Si une fonction traite des milliers d’enregistrements en un seul tick, elle devrait être déchargée sur un worker thread.
  • Examinez les utilisations de `JSON.parse` et `JSON.stringify` sur des charges utiles très volumineuses, car celles-ci sont synchrones et peuvent bloquer la boucle lorsque les données sont suffisamment volumineuses.

Mauvaise pratique :

app.get('/config', (req, res) => {
  const config = fs.readFileSync('./config.json', 'utf8'); // bloque toutes les requêtes
  res.json(JSON.parse(config));
});

Bonne pratique (Éviter de bloquer la boucle d’événements) :

app.get('/config', async (req, res, next) => {
  try {
    const config = await fs.promises.readFile('./config.json', 'utf8');
    res.json(JSON.parse(config));
  } catch (err) {
    next(err);
  }
});

Gestion de `async/await` et des Promises :

  • Assurez-vous que chaque fonction `async` dispose d’une gestion d’erreurs appropriée ; une rejection de Promise non gérée dans Node.js terminera le processus dans les versions plus récentes.
  • Vérifiez les appels `await` séquentiels inutiles où les opérations pourraient s’exécuter en parallèle à l’aide de `Promise.all`.

Mauvaise pratique :

// Séquentiel — utilisateur et commandes récupérés l’un après l’autre
const user = await fetchUser(id);
const orders = await fetchOrders(id);

Bonne pratique :

// Parallèle — les deux récupérés simultanément
const [user, orders] = await Promise.all([fetchUser(id), fetchOrders(id)]);
  • Confirmez que des gestionnaires `.catch()` sont attachés à toutes les Promises autonomes qui ne sont pas `await`-ées.
  • Recherchez les fonctions `async` appelées à l’intérieur de boucles `forEach`. Ce modèle n’attend pas la fin du travail asynchrone et masque silencieusement les erreurs. Utilisez `Promise.all` avec `.map()` à la place.

Worker threads pour les tâches gourmandes en CPU :

  • Identifiez toute opération gourmande en CPU telle que le traitement d’images, les calculs cryptographiques ou les transformations de données volumineuses.
  • Vérifiez que ces tâches sont déchargées sur des worker threads à l’aide du module `worker_threads` intégré à Node.js, plutôt que de bloquer le thread principal.

Gestion des erreurs

Une application qui gère les erreurs avec élégance est une application à laquelle les opérateurs font confiance. Si la vôtre ne répond pas à cette exigence, c’est un incident de production qui attend de se produire.

Propagation cohérente des erreurs :

  • Vérifiez que les erreurs sont passées au middleware Express en utilisant `next(err)` plutôt que d’être gérées de manière ad hoc à l’intérieur de chaque route. Un gestionnaire d’erreurs centralisé maintient un comportement cohérent et centralise les journaux.
  • Assurez-vous que les classes d’erreurs personnalisées étendent l’objet `Error` intégré et transportent un message et un `statusCode` significatifs.
class AppError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode;
    this.isOperational = true;
  }
}

Distinguer les erreurs opérationnelles des erreurs de programmation :

  • Les erreurs opérationnelles sont attendues : un utilisateur envoie des données invalides, une connexion à la base de données expire, une API tierce renvoie un 503. Celles-ci doivent être interceptées, enregistrées et renvoyées au client avec une réponse significative.
  • Les erreurs de programmation sont des bugs : accès à une propriété de `undefined`, appel d’une fonction inexistante. Celles-ci devraient faire planter le processus et être interceptées par un gestionnaire de processus comme PM2 ou une politique de redémarrage de conteneur.
  • Confirmez que la base de code n’étouffe pas silencieusement les erreurs de programmation dans les blocs `try/catch` avec un corps `catch` vide.

Rejections non gérées et exceptions non interceptées :

  • Vérifiez que l’application enregistre des gestionnaires pour `process.on(‘unhandledRejection’)` et `process.on(‘uncaughtException’)` afin d’enregistrer l’erreur avant de sortir, plutôt que de planter sans laisser de trace.
  • Assurez-vous que l’application tente un arrêt gracieux (fermeture des connexions serveur et des pools de base de données) avant d’appeler `process.exit(1)` afin d’éviter de perdre des requêtes en cours.
process.on('unhandledRejection', (reason) => {
  logger.error('Unhandled rejection:', reason);
  process.exit(1);
});

Bonnes pratiques de sécurité

La vérité est que les problèmes de sécurité de Node.js constituent une menace réelle. Par conséquent, vous devez en tenir compte dès le premier jour de travail sur le projet. Rien qu’en 2024, des packages de haut profil, y compris `web3-utils` (CVE-2024-21505) et `dset` (CVE-2024-21529), ont été trouvés contenir des vulnérabilités de pollution de prototype qui pourraient conduire à une exécution de code à distance. La taille de l’écosystème npm est sa plus grande force et l’une de ses plus importantes surfaces de risque.

Prévention de la pollution de prototype :

  • Recherchez tout code qui fusionne ou clone des objets fournis par l’utilisateur sans validation, tels que des fonctions de fusion récursive et des utilitaires de clonage profond sur des entrées non fiables, qui sont un point d’entrée courant pour la pollution de prototype.
  • Assurez-vous que les objets qui traitent les entrées utilisateur sont créés avec `Object.create(null)` lorsque cela est approprié, en supprimant entièrement la chaîne de prototype.
  • Vérifiez que les charges utiles JSON provenant de sources externes sont validées par rapport à un schéma avant d’être traitées.

Validation des entrées et prévention des injections :

  • Confirmez que toutes les données de requête entrantes, les paramètres de requête, les en-têtes et les champs du corps sont validés avant d’être utilisés. Des bibliothèques comme `zod`, `joi` ou `express-validator` sont des outils standard pour cela.
  • Vérifiez toutes les requêtes de base de données pour les entrées paramétrées, car les requêtes concaténées en chaînes sont un chemin direct vers l’injection SQL.
  • Examinez toute utilisation de `eval()`, `new Function()` ou `child_process.exec()` avec une entrée dynamique. Ceux-ci doivent être traités comme des vulnérabilités critiques, sauf justification explicite et bien documentée.

En-têtes HTTP de sécurité et HTTPS :

  • Confirmez que `helmet` ou un middleware équivalent est en place pour définir les en-têtes HTTP pertinents pour la sécurité, y compris `Content-Security-Policy`, `X-Frame-Options` et `Strict-Transport-Security`.
  • Vérifiez que l’application impose HTTPS en production et n’accepte pas les requêtes HTTP simples pour les routes authentifiées.
  • Vérifiez que les paramètres de cookie incluent les indicateurs `Secure`, `HttpOnly` et `SameSite`.

Limitation de débit et protection contre le déni de service :

  • Vérifiez que les points d’accès API, en particulier les points d’accès d’authentification, ont une limitation de débit en place à l’aide d’un middleware tel que `express-rate-limit`.
  • Recherchez les expressions régulières appliquées aux entrées utilisateur ; des modèles inefficaces peuvent être exploités pour consommer du temps CPU dans ce qui est connu sous le nom d’attaque ReDoS.

Secrets et authentification :

  • Confirmez que les secrets JWT, les clés API et les informations d’identification de la base de données ne sont jamais codés en dur dans les fichiers source ni validés dans le contrôle de version.
  • Examinez la logique de validation des jetons et assurez-vous que des restrictions d’algorithme sont en place pour les JWT afin d’empêcher la vulnérabilité de l’algorithme `none`.
  • Vérifiez que le hachage des mots de passe utilise `bcrypt`, `argon2` ou `scrypt`, et non `MD5`, `SHA-1` ou `SHA-256` simple sans sel.

Optimisation des performances

Une application Node.js qui s’exécute rapidement en développement se comporte souvent très différemment sous la charge de production. Les problèmes de performance s’accumulent rapidement lorsque vous avez de nombreuses connexions concurrentes.

Efficacité des requêtes de base de données :

  • Examinez toutes les requêtes de base de données pour les schémas N+1. Si une route effectue une requête pour obtenir une liste d’enregistrements, puis une requête distincte par enregistrement pour récupérer les données associées, elle ralentira considérablement à grande échelle.
  • Confirmez que des index de base de données sont en place pour tous les champs utilisés dans les clauses `WHERE`, les `JOIN` et les expressions `ORDER BY`.
  • Vérifiez les requêtes illimitées, car toute requête pouvant retourner un nombre arbitraire de lignes sans pagination constitue un problème futur de mémoire et de temps de réponse.

Mise en cache :

  • Identifiez les opérations coûteuses, telles que les requêtes de base de données complexes ou les appels d’API externes, qui pourraient bénéficier de la mise en cache.
  • Confirmez qu’une couche de mise en cache, telle que Redis ou un cache en mémoire, est en place pour les données fréquemment consultées et peu changeantes.
  • Vérifiez que la logique d’invalidation du cache existe, car un cache qui grandit indéfiniment ou qui sert des données obsolètes est pire qu’aucun cache.

Pool de connexions et gestion des ressources :

  • Confirmez que les connexions à la base de données utilisent un pool plutôt que d’ouvrir une nouvelle connexion par requête. C’est une erreur courante qui amène les applications Node.js à épuiser les limites de connexions de la base de données sous charge.
  • Vérifiez que les clients HTTP et les connexions aux services externes sont réutilisés plutôt que créés par requête.

Streaming pour les données volumineuses :

  • Examinez les points d’accès qui lisent de gros fichiers ou des ensembles de résultats de base de données en mémoire avant de les envoyer au client. Ceux-ci devraient utiliser les flux Node.js pour acheminer les données de manière incrémentielle plutôt que de tout mettre en mémoire tampon d’abord.

Gestion des dépendances

Il existe plus de 2 millions de packages dans le registre npm. À travers eux, chaque projet Node.js porte un arbre de dépendances qui s’étend bien au-delà de ce que toute équipe contrôle directement. Par conséquent, l’arbre lui-même est une surface de risque que vous devez gérer efficacement.

Maintenir les dépendances à jour et minimales :

  • Exécutez `npm audit` et vérifiez que toutes les vulnérabilités élevées et critiques ont été résolues ou ont une exception explicite et documentée.
  • Examinez `package.json` pour les dépendances qui ne sont plus utilisées, car les packages inutilisés augmentent le temps d’installation et la surface d’attaque sans apporter aucune valeur.
  • Vérifiez que les plages de versions dans `package.json` ne sont pas trop permissives. Bien que le Versionnement Sémantique stipule que les mises à jour mineures doivent être rétrocompatibles, une plage caret comme `^4.0.0` peut introduire des changements majeurs si les auteurs de packages violent la norme ; `package-lock.json` ou `yarn.lock` doit être validé pour garantir des installations reproductibles.

Évaluation des packages tiers :

  • Pour toute dépendance nouvellement ajoutée, vérifiez son volume de téléchargements npm, sa date de dernière publication et le nombre d’avis de sécurité ouverts.
  • Privilégiez les packages activement maintenus et ayant un historique de propriété clair, car un package publié il y a quatre ans sans activité est un risque pour la chaîne d’approvisionnement.
  • Vérifiez que les bibliothèques utilitaires volumineuses ne sont pas importées juste pour une seule fonction ; importer `lodash` en entier ajoute des kilo-octets là où une seule méthode native ferait le même travail.

Tests et couverture de code

Les tests ne sont pas une bureaucratie mais plutôt le mécanisme par lequel une équipe communique ce que le code est censé faire, et le seul moyen fiable de savoir si un changement a cassé quelque chose.

Couverture des tests unitaires et d’intégration :

  • Vérifiez que toutes les fonctions de service et les modules utilitaires ont des tests unitaires couvrant le chemin nominal, les cas limites et les conditions d’erreur.
  • Confirmez que les gestionnaires de routes ont des tests d’intégration qui valident le cycle complet requête-réponse, y compris les middlewares d’authentification.
  • Vérifiez que la suite de tests s’exécute sans appels réseau ou base de données, sauf si ces appels sont des tests d’intégration intentionnels, car les tests unitaires doivent utiliser des mocks pour les dépendances externes.

Qualité des tests, pas seulement les chiffres de couverture :

  • Examinez si les tests portent sur des résultats significatifs plutôt que de simplement vérifier qu’une fonction a été appelée ; un test qui vérifie seulement `toHaveBeenCalled` sans valider le résultat ne protège pas le comportement.
  • Assurez-vous que les noms de tests décrivent le comportement testé, par exemple, `it(‘returns 401 when the token is expired’)` plutôt que `it(‘handles auth’)`.
  • Confirmez que le pipeline CI échoue en cas d’échec des tests et qu’aucun test n’est ignoré avec `it.skip` ou `xit` sans raison documentée.

Configuration d’environnement et secrets

Lorsque vous constatez l’écart entre le comportement d’une application Node.js en développement et en production, vous êtes face à des problèmes qui trouvent leur origine dans la configuration.

Gestion des variables d’environnement :

  • Confirmez que toute la configuration spécifique à l’environnement, y compris les URL de base de données, les clés API, les indicateurs de fonctionnalités et les numéros de port, provient des variables d’environnement plutôt que de valeurs codées en dur.
  • Vérifiez qu’un fichier `.env.example` existe dans le dépôt, documentant toutes les variables requises sans leurs valeurs réelles. Le fichier `.env` réel doit être dans `.gitignore`.
  • Vérifiez que l’application échoue bruyamment au démarrage si les variables d’environnement requises sont manquantes, plutôt que de s’exécuter dans un état partiellement configuré qui produit des erreurs subtiles plus tard.
// Valider les variables d’environnement requises au démarrage
const required = ['DATABASE_URL', 'JWT_SECRET', 'PORT'];
for (const key of required) {
  if (!process.env[key]) {
    throw new Error(`Missing required environment variable: ${key}`);
  }
}

Configuration de production vs développement :

  • Vérifiez que `NODE_ENV` est correctement défini dans tous les environnements de déploiement et que l’application se comporte de manière appropriée dans chaque mode, par exemple, des messages d’erreur verbaux uniquement en développement.
  • Confirmez que les outils de débogage et la journalisation verbale sont désactivés dans les builds de production.

Journalisation et observabilité

Imaginez que vous receviez une alerte à 2 heures du matin indiquant que quelque chose s’est mal passé. La différence entre une correction de 15 minutes pour ce problème et une enquête de plusieurs heures sera déterminée par la qualité de vos journaux.

Journalisation structurée :

  • Vérifiez que l’application utilise une bibliothèque de journalisation structurée telle que `pino` ou `winston` plutôt que des instructions `console.log` dispersées dans la base de code. Les journaux structurés sont consultables et compatibles avec les outils d’agrégation de journaux.
  • Assurez-vous que les entrées de journal incluent un horodatage, un niveau de journal, un ID de requête et suffisamment de contexte pour comprendre ce qui s’est passé sans avoir à deviner.

Niveaux de journal significatifs :

  • Confirmez que les niveaux de journal sont utilisés correctement : `info` pour les opérations normales, `warn` pour les conditions inattendues mais non critiques, `error` pour les défaillances nécessitant une attention, et `debug` pour les données de diagnostic détaillées qui ne devraient jamais apparaître en production.
  • Recherchez les appels `console.log` laissés dans le code de production. Ce sont le signe que la journalisation n’a pas été correctement configurée et que des informations importantes peuvent aller au mauvais endroit.

Traçage des requêtes et vérifications d’état de santé :

  • Vérifiez que l’application attribue un ID de requête unique à chaque requête entrante et le propage à travers toutes les entrées de journal pour cette requête. Cela permet de suivre la requête d’un utilisateur unique à travers un système distribué.
  • Confirmez qu’un point d’accès de vérification d’état de santé existe et renvoie une réponse significative indiquant si l’application et ses dépendances sont opérationnelles.

Revue de code Node.js avec Redwerk

Parcourir cette checklist par vous-même est un bon début, mais la prochaine étape consiste à demander à une équipe externe de l’exécuter pour vous. Chez Redwerk, nous construisons et revoyons des applications depuis plus de deux décennies. Notre équipe couvre l’ensemble du tableau : architecture, modèles asynchrones, sécurité, performances et tests. Nous signalons ce qui ne va pas, expliquons pourquoi c’est important et vous donnons une voie claire pour le corriger.

Jetons un coup d’œil à la manière dont cela fonctionne en pratique à travers notre étude de cas pour Kooky, un système intelligent de gobelets réutilisables que nous avons construit à partir de zéro. C’est une plateforme pour le marché suisse qui fonctionne sur un backend temps réel piloté par événements. Actuellement, elle gère le suivi des gobelets basé sur QR, les mises à jour de compte en direct et le traitement instantané des dépôts. Elle opère en partenariat avec les Chemins de fer fédéraux suisses, Valora et Coop.

Afin de maintenir la stabilité de ce système sous charge concurrente, nous avons dû cocher tous les éléments de cette liste dès le premier jour, notamment :

  • Des modèles asynchrones qui ne jamais affamer la boucle d’événements
  • La limitation de débit sur les points d’accès de paiement
  • Une journalisation structurée qui rend les incidents de production traçables
  • Un arbre de dépendances qui est audité à chaque version

Avec tout cela en place, Kooky est devenu la startup numéro un en technologie verte en Suisse, et une bonne architecture y a beaucoup contribué.

Plus de 50 entreprises nous ont fait confiance pour construire ou auditer leurs produits à partir de zéro. Et que votre projet Node.js soit une API de startup ou une plateforme d’entreprise, notre expérience en développement Node.js signifie que nous savons exactement où les problèmes ont tendance à se cacher.

Si votre base de code mérite un examen plus approfondi, contactez-nous, et nous nous en occuperons.

Voyez la différence qu’une revue de code peut faire : comment nous avons identifié plus de 80 améliorations et risques de sécurité pour une place de marché mobile

Veuillez saisir votre adresse courriel professionnelle n'est pas un courriel professionnel