Construire un package npm propre change vite la qualité d’une base backend : on réutilise mieux le code, on réduit les doublons et on publie des API plus stables. Le vrai sujet n’est pas seulement le code, mais la surface publique, la version, les fichiers embarqués et la manière de livrer sans exposer de secrets. Dans cet article, je vais aller droit au but : structure du projet, publication, sécurité, tests locaux et choix entre paquets publics, scoped ou privés.
Les points clés à garder avant la première publication
- Un paquet utile répond à un besoin récurrent : client HTTP, validation, auth, logging ou SDK d’API.
- `name` et `version` sont obligatoires, et la version doit suivre SemVer.
- `npm pack --dry-run` permet de voir exactement ce qui part dans le tarball avant publication.
- Les packages scoped sont souvent le meilleur choix pour une équipe, une organisation ou un SDK backend.
- Pour la release, je privilégie les tests automatiques, `prepublishOnly` et la publication de confiance via OIDC quand c’est possible.
Définir le bon périmètre avant d’écrire le code
Avant de créer un paquet, je commence par une question simple : est-ce que ce code va vraiment servir à plusieurs projets, ou seulement à un service isolé ? Dans un contexte backend et API, les bons candidats sont souvent très concrets : un client HTTP interne, un wrapper d’authentification, un validateur de payload, un logger structuré, un gestionnaire de rate limit ou un SDK pour consommer votre propre API.
Le paquet devient intéressant quand il a trois qualités : il résout un problème récurrent, il a un contrat stable et il ne dépend pas trop du contexte local. Dès qu’une bibliothèque suppose la structure exacte d’un monorepo, des variables d’environnement très spécifiques ou des accès réseau internes impossibles à reproduire, je me méfie. Ce type de code est parfois utile, mais il ne mérite pas forcément une publication publique.
- Bon cas d’usage : un client API réutilisable dans 3 services différents.
- Bon cas d’usage : une couche de validation qui protège plusieurs routes ou jobs.
- Cas plus fragile : une fonction utilitaire utilisée une seule fois dans une application.
- Cas à éviter : un paquet qui expose des détails d’infrastructure trop liés à un seul projet.
Dans la pratique, je préfère un paquet petit, lisible et prévisible à une bibliothèque trop ambitieuse. Une fois le périmètre fixé, je passe au fichier qui contrôle presque tout : package.json.
Construire un paquet propre autour de package.json
Pour publier un package npm propre, je veux d’abord un package.json cohérent. Ce fichier n’est pas un simple détail administratif : il définit l’identité du paquet, son point d’entrée, ce qui part dans le registre et les scripts qui sécurisent la release.
| Champ | Pourquoi je le renseigne |
|---|---|
name |
Nom public, en minuscules, sans espaces. Je le scope souvent pour éviter les collisions entre équipes ou organisations. |
version |
Identifiant unique de la version. Je pars presque toujours de 1.0.0 pour un vrai paquet stable. |
description |
Phrase courte qui aide à comprendre le rôle du paquet et améliore sa lisibilité dans le registre. |
main / exports
|
Point d’entrée du module. J’utilise exports dès que je veux contrôler précisément l’API publique. |
files |
Liste blanche des fichiers à publier. C’est mon garde-fou contre les sources inutiles et les artefacts oubliés. |
scripts |
Automatisation locale : build, test, lint et vérifications de release. |
private |
Je l’active pour un projet interne qui ne doit jamais être publié par erreur. |
{
"name": "@mon-org/api-client",
"version": "1.0.0",
"description": "Client HTTP pour API internes",
"type": "module",
"exports": {
".": "./dist/index.js"
},
"files": ["dist", "README.md", "LICENSE"],
"scripts": {
"build": "tsc -p tsconfig.json",
"test": "vitest run",
"prepublishOnly": "npm run build && npm test"
}
}Je mets les outils de build dans devDependencies et les vraies dépendances d’exécution dans dependencies. Si je publie du TypeScript, je compile avant la mise en ligne, parce que je préfère exposer du JavaScript stable plutôt que forcer le consommateur à reproduire ma chaîne de build. Quand le contrat du paquet est clair, je vérifie ce qui part réellement dans le tarball.
Contrôler ce qui part dans le tarball
Le meilleur réflexe avant publication reste npm pack --dry-run. Cette commande me montre le contenu final du paquet sans rien envoyer au registre. C’est souvent là que je repère les erreurs qui coûtent le plus cher : un fichier de configuration sensible, des tests qui n’ont rien à faire dans la distribution, un dossier de build incomplet ou un README absent.
Par défaut, npm inclut largement le contenu du dossier, mais certaines règles changent vite la donne. Le fichier README.md, le package.json et le fichier de licence partent généralement avec le paquet. En revanche, si je définis files, je transforme ce champ en liste blanche. Et si j’utilise .npmignore, il prend la main sur .gitignore pour ce qui ne doit pas être empaqueté.
- Je retire toujours les secrets : clés privées, tokens, mots de passe, dumps de base ou données personnelles.
- Je n’embarque que le build final, pas les sources de travail inutiles.
- Je vérifie que la documentation utilisateur est présente et à jour.
- Je contrôle la taille du tarball, surtout pour un SDK d’API ou une lib backend consommée souvent en CI.
Un paquet privé n’est pas un coffre-fort parfait : si je laisse une information sensible dedans, je considère qu’elle peut finir par sortir un jour. Une fois le contenu maîtrisé, je teste le paquet comme le ferait réellement un autre projet.
Tester comme un vrai consommateur
Le test qui compte le plus pour moi est simple : installer le paquet dans un projet vierge, sans le relier à mon environnement de développement. C’est ce test qui révèle les oublis de build, les chemins cassés, les imports trop optimistes et les dépendances mal déclarées.
mkdir /tmp/test-package
cd /tmp/test-package
npm init -y
npm install /chemin/vers/le/paquetJe teste aussi le tarball produit par npm pack, parce qu’il ressemble davantage à ce qui sera téléchargé depuis le registre. Si le paquet doit marcher en CommonJS et en ESM, je vérifie les deux. Si l’objectif est un backend ou un client d’API, je fais au moins un test d’intégration minimal dans un service de démonstration ou un projet de recette.
npm link reste pratique pour l’itération rapide, mais je ne lui fais pas confiance pour valider la publication finale. Les symlinks peuvent masquer des problèmes que la vraie installation ferait ressortir immédiatement. Quand le paquet passe ce filtre, je regarde enfin comment le publier proprement selon sa visibilité.

Choisir entre public, scoped et privé
La visibilité du paquet change sa manière d’être consommé, sa sécurité et parfois même sa stratégie de nommage. Dans les projets backend et API, je trouve souvent le scoped package plus naturel, surtout quand plusieurs équipes partagent le code ou quand le nom doit rester lié à une organisation.
| Type | Quand je le choisis | Point d’attention |
|---|---|---|
| Unscoped public | Bibliothèque générique, utilitaire open source, module très simple à adopter. | Le nom doit être unique et le paquet est public par nature. |
| Scoped public | SDK d’API, utilitaire d’équipe, paquet d’organisation ou librairie de plateforme. | Je publie souvent avec npm publish --access public au premier envoi. |
| Scoped privé | Code partagé en interne, outil de production ou paquet réservé à un groupe restreint. | Il faut gérer les accès avec soin et éviter les dépendances trop opaques. |
Pour du backend, je privilégie souvent le scoped public si le paquet est destiné à être réutilisé par d’autres projets de l’entreprise ou par des partenaires. Cela évite les conflits de nom et rend l’origine du code immédiatement lisible. Si le code ne doit rester qu’en interne, je garde le scope privé ou je bloque la publication avec private: true.
Publier sans fragiliser la release
Quand je dois publier un paquet, je ne pousse jamais directement sans vérifier le tarball et le script de release. Je pars d’une logique simple : la version doit raconter la nature du changement. Avec SemVer, un correctif reste en patch, une fonctionnalité compatible en minor et une rupture en major.
- Je mets à jour la version de façon explicite, par exemple
1.2.3vers1.2.4,1.3.0ou2.0.0. - Je lance le build et les tests sur un environnement propre.
- Je relance
npm pack --dry-runpour vérifier le contenu final. - Je publie avec le bon niveau d’accès et le bon tag de distribution.
- Je surveille les retours après la mise en ligne, surtout sur les installations et les imports.
Je garde aussi en tête une contrainte importante : une combinaison nom + version publiée ne doit pas être réutilisée. Autrement dit, je traite chaque release comme un artefact immuable. Pour les préversions, j’aime utiliser des tags comme next ou beta afin de séparer clairement les versions stables des versions de test.
Sur la sécurité, la tendance que je privilégie aujourd’hui est la publication de confiance via OIDC quand la CI le permet. En pratique, cela évite les tokens longue durée et réduit le risque de fuite dans les logs ou dans les variables d’environnement. Le flux est compatible avec les principaux environnements CI, et il demande une configuration précise du workflow, mais le gain en sécurité est réel. Si je reste sur une publication manuelle, je garde au minimum une authentification forte et des permissions réduites.
Une release qui se passe bien, ce n’est pas seulement un npm publish réussi. C’est une chaîne complète qui part du build, passe par le test du tarball, choisit le bon tag et finit sans surprise pour l’équipe consommatrice. Une fois la première version sortie, le vrai travail de maintenance commence.
Faire vivre le paquet après la première version
Le plus dur n’est pas de publier une fois, mais de garder le paquet utile sans casser les intégrations. C’est particulièrement vrai pour les bibliothèques backend et API, parce que plusieurs services dépendent souvent de la même surface publique. À ce stade, je me concentre sur la discipline de version, la documentation et les canaux de diffusion.
- Je maintiens un changelog clair, avec les ruptures signalées sans ambiguïté.
- Je déprécie les anciennes versions avec un message utile plutôt que de laisser les consommateurs découvrir le problème seuls.
- Je réserve
latestà la version stable et j’utilise un tag séparé pour les préversions. - Je garde les exemples d’usage alignés sur l’API réellement exposée.
- Je teste le paquet sur les versions de Node que je veux vraiment supporter.
Si je ne devais retenir qu’une règle, ce serait celle-ci : un bon paquet backend ne se juge pas à la vitesse du premier publish, mais à la qualité de ses mises à jour. Plus la surface publique est petite, plus le build est reproductible et plus la publication est sécurisée, plus le paquet reste facile à adopter. C’est exactement ce qui fait la différence entre une bibliothèque réutilisable et un artefact qu’on évite au bout de deux releases.