backend / nodejs
npm et package.json
Explication
Ce que vous allez apprendre
- Comprendre le format SemVer (
MAJEUR.MINEUR.PATCH) et les symboles^/~ - Distinguer
dependenciesetdevDependenciesdanspackage.json - Comprendre pourquoi
package-lock.jsondoit toujours être commité - Utiliser
npm ciplutôt quenpm installen CI et en production - Détecter des vulnérabilités connues avec
npm audit
Dans quel contexte ?
Une application fonctionne parfaitement sur la machine d'un développeur, mais le déploiement en production échoue avec une erreur provenant d'une dépendance qui s'est mise à jour silencieusement vers une version incompatible. En creusant, l'équipe découvre que package-lock.json a été ajouté par erreur au .gitignore du projet plusieurs semaines auparavant. Cette leçon explique pourquoi ce fichier est indispensable et comment npm gère les versions pour éviter ce genre d'incident.
D'abord, à quoi sert réellement npm
Aucune application moderne ne se construit entièrement à partir de zéro. On s'appuie sur des librairies écrites par d'autres — Express, une lib de validation, un driver de base de données.
npm est l'outil qui gère tout ça. Il télécharge ces librairies, gère leurs versions, et garde une trace de ce dont ton projet dépend, via le fichier package.json.
Pour comprendre les versions, il faut d'abord connaître leur format. Une version comme 4.19.2 suit toujours le schéma MAJEUR.MINEUR.PATCH.
Deux symboles devant ce numéro changent tout : ^ et ~. ^4.19.2 autorise npm à installer automatiquement n'importe quelle version 4.x.x plus récente, alors que ~4.19.2 ne permet que des correctifs de bugs (4.19.x).
Ce choix a un impact réel sur la stabilité du projet dans le temps. Une plage trop large peut introduire un changement inattendu ; une version figée peut priver le projet de correctifs de sécurité importants.
| Symbole | Exemple | Plage autorisée |
|---|---|---|
^ (caret) | ^4.19.2 | >= 4.19.2, < 5.0.0 |
~ (tilde) | ~4.19.2 | >= 4.19.2, < 4.20.0 |
| Aucun | 4.19.2 | Uniquement cette version exacte |
Prérequis
Aucune connaissance préalable n'est nécessaire, mais avoir déjà installé un paquet avec npm install aide à situer les exemples de cette leçon.
Une fois ces symboles compris, il reste un problème à résoudre : package.json décrit des PLAGES, pas des versions exactes. Deux installations à des moments différents pourraient donc récupérer des versions légèrement différentes de certaines dépendances.
C'est exactement ce que package-lock.json empêche. Il fige EXACTEMENT l'arborescence complète des dépendances, garantissant des installations identiques sur toutes les machines et en CI.
Une fois ce fichier en place, une distinction pratique mérite d'être connue : npm install contre npm ci. npm ci (pour "clean install") installe EXACTEMENT ce que dit le lock file, et échoue immédiatement si package.json et le lock ne sont pas cohérents.
C'est pour cette raison qu'on utilise npm ci en CI et en production plutôt que npm install. Ce dernier pourrait silencieusement mettre à jour des versions sans qu'on s'en rende compte.
Le piège le plus fréquent à éviter : oublier de commiter package-lock.json, ou pire, l'ajouter par erreur au .gitignore. Ça casse la reproductibilité des installations entre les développeurs de l'équipe et l'environnement de production — un problème qui peut prendre des heures à diagnostiquer.
Piège fréquent
Ajouter package-lock.json au .gitignore "pour alléger le dépôt" casse la promesse d'installations identiques partout : deux machines exécutant npm install au même moment peuvent obtenir des sous-dépendances légèrement différentes. Ce fichier doit toujours être commité, jamais ignoré.
Commandes & code
npm et package.json
npm init -y # génère un package.json par défaut
npm install express # dépendance de production
npm install -D vitest supertest # dépendance de développement uniquement
npm install express@4.19.2 # version exacte
npm uninstall lodash
npm outdated # liste les paquets obsolètes
npm audit # scan de vulnérabilités connues
npm audit fix # corrige automatiquement ce qui est possible// package.json
{
"name": "mon-api",
"version": "1.4.0",
"private": true,
"type": "module",
"engines": {
"node": ">=20.0.0"
},
"scripts": {
"dev": "node --watch src/index.js",
"start": "node src/index.js",
"test": "vitest run",
"test:watch": "vitest",
"lint": "eslint src --ext .js",
"build": "tsc -p tsconfig.json"
},
"dependencies": {
"express": "^4.19.2",
"zod": "^3.23.8",
"pg": "^8.11.5"
},
"devDependencies": {
"vitest": "^1.6.0",
"supertest": "^7.0.0",
"eslint": "^9.0.0"
}
}^4.19.2 -> accepte 4.x.x (>= 4.19.2, < 5.0.0) — mises à jour mineures/patchs
~4.19.2 -> accepte 4.19.x (>= 4.19.2, < 4.20.0) — patchs uniquement
4.19.2 -> version exacte, aucune mise à jour automatique
* -> n'importe quelle version (déconseillé en production)# package-lock.json fige les versions EXACTES de toute l'arborescence de dépendances
# TOUJOURS le commiter — garantit des installations identiques sur toutes les machines/CI
npm ci # installation stricte à partir du lock file — utilisé en CI/production
# (plus rapide que "npm install", échoue si package.json et lock divergent)// Scripts avec hooks automatiques (pre/post)
{
"scripts": {
"prebuild": "npm run lint",
"build": "tsc",
"postbuild": "echo Build terminé",
"prepare": "husky install"
}
}# Workspaces npm — monorepo avec plusieurs paquets liés
npm install -w packages/api express
npm run test --workspaces// package.json racine d'un monorepo
{
"name": "mon-monorepo",
"private": true,
"workspaces": ["packages/*"]
}Résumé
^(caret) et~(tilde) contrôlent la portée des mises à jour automatiques selon le SemVer.package-lock.jsondoit toujours être commité pour garantir des installs reproductibles.npm ci(et nonnpm install) en CI/production : plus rapide, plus strict.- Les workspaces npm gèrent nativement les monorepos multi-paquets sans outil tiers.
Exercices pratiques
Mission : reproduire un bug de production introuvable en local
Objectif : Diagnostiquer une divergence de version causée par un package-lock.json ignoré, et sécuriser durablement le processus d'installation.
Contexte
Une application fonctionne parfaitement en local, mais le déploiement en production échoue au démarrage avec une erreur provenant d'une dépendance interne à express incompatible. En creusant l'historique git, tu découvres que quelqu'un a ajouté package-lock.json au .gitignore trois semaines plus tôt "pour alléger le dépôt", et que le pipeline CI exécute npm install (pas npm ci) avant chaque déploiement.