Die Manifest-Datei für ein Node-Projekt
Jedes Node.js-Projekt hat eine package.json im Wurzelverzeichnis. Diese simple JSON-Datei beschreibt dein Projekt – Name, Version, Abhängigkeiten, verfügbare Befehle – und ist die zentrale Anlaufstelle für npm. Ohne sie weiß der Node.js Package Manager schlicht nicht, womit er es zu tun hat.
Am schnellsten legst du sie mit npm init an:
npm init -y
Mit dem Flag -y überspringst du alle Rückfragen und übernimmst die Standardwerte. Heraus kommt dann so etwas:
{
"name": "my-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
Das ist das nackte Grundgerüst. Die meisten dieser Felder bringen für sich allein wenig – ihr Nutzen zeigt sich erst, wenn du Dependencies und Scripts hinzufügst.
dependencies vs devDependencies
Zwei Felder übernehmen praktisch die ganze Arbeit: dependencies und devDependencies. Beide sind Maps, die Paketnamen auf Versionsbereiche abbilden.
Der Unterschied ist aus einem Grund wichtig: dependencies sind Pakete, die dein Code zur Laufzeit braucht. devDependencies brauchst du nur während der Entwicklung — also Test-Runner, Linter, Build-Tools, Type-Checker. Wenn jemand dein Paket als Abhängigkeit in seinem Projekt installiert, lädt npm nur deine dependencies herunter und ignoriert die devDependencies.
npm pflegt diese Felder automatisch. npm install express trägt eine Zeile unter dependencies ein, npm install --save-dev vitest landet bei den devDependencies. Von Hand bearbeitest du die beiden Blöcke praktisch nie.
Versionsbereiche in package.json: ^, ~ und feste Versionen
Versionsangaben wie ^4.19.0 sind keine exakten Versionen, sondern Bereiche (Ranges). npm folgt dabei dem Semver-Standard, der eine Versionsnummer in MAJOR.MINOR.PATCH zerlegt:
- MAJOR — erhöht sich, wenn es Breaking Changes gibt.
- MINOR — kommt neu dazu, wenn Features ergänzt werden, ohne Bestehendes zu brechen.
- PATCH — steht für Bugfixes.
Diese beiden Operatoren begegnen dir ständig:
"express": "^4.19.0" // >= 4.19.0 und < 5.0.0 (jede 4.x.x ab 4.19.0)
"express": "~4.19.0" // >= 4.19.0 und < 4.20.0 (jede 4.19.x ab 4.19.0)
"express": "4.19.0" // exakt 4.19.0
^ ist die Voreinstellung von npm beim Installieren eines Pakets. Man vertraut darauf, dass Minor- und Patch-Updates kompatibel bleiben. ~ ist zurückhaltender – hier sind nur Patch-Updates erlaubt. Eine nackte Versionsnummer pinnt exakt auf diese Version.
Der Haken: „Was ich gerade installiert habe" und „was der Range zulässt" sind nicht dasselbe. Installierst du heute express@4.19.0 und ein Kollege zieht sich dein Projekt in einem Monat, löst ^4.19.0 bei ihm vielleicht schon auf 4.19.5 auf. Genau dafür gibt es die package-lock.json – sie hält die exakt aufgelösten Versionen fest, damit am Ende bei allen derselbe Abhängigkeitsbaum landet. Also: unbedingt committen.
Scripts: Die Kommandozentrale deines Projekts
Im Feld scripts definierst du Shortcuts für häufig genutzte Befehle. Alles, was dort steht, kannst du mit npm run <name> ausführen:
Ein paar Dinge, die du über Scripts wissen solltest:
npm start,npm testund eine Handvoll weiterer Namen funktionieren ohne das Keywordrun. Für alles andere brauchst dunpm run <name>.- Scripts laufen in einer Shell, bei der
node_modules/.binimPATHliegt. Dadurch kannst du Binaries aus installierten Paketen direkt aufrufen."test": "vitest"funktioniert also, obwohlvitestgar nicht global installiert ist. - Scripts lassen sich verketten:
"build": "npm run lint && npm run compile". Mit&&heißt das: „nacheinander ausführen, bei einem Fehler abbrechen". - Scripts mit dem Präfix
pre<name>undpost<name>laufen automatisch mit. Wenn du z. B. einprebuild-Script hast, wird es ohne weiteres Zutun vorbuildausgeführt.
Scripts sind die Kommandozentrale deines Projekts. Eine saubere package.json sorgt dafür, dass neue Mitwirkende einfach klonen, npm install ausführen und dann mit npm run dev bzw. npm test loslegen können – ganz ohne Wiki.
Einstiegspunkte: main, exports und type
Diese Felder sagen Node (und Bundlern), wie dein Package geladen werden soll.
typelegt fest, wie.js-Dateien geparst werden."module"steht für ESM (import/export). Lässt du das Feld weg oder setzt"commonjs", nutzt Node CommonJS (require). Details dazu findest du im Artikel zu CommonJS vs. ESM.mainist der klassische Einstiegspunkt — also das, wasrequire("my-lib")auflöst. Ältere Tools werten das Feld nach wie vor aus.exportsist der moderne, strengere Nachfolger. Hier legst du exakt fest, welche Dateien von außen unter welchem Subpfad importiert werden dürfen. Alles, was nicht drinsteht, lässt sich nicht importieren — und das ist gewollt, kein Bug. So behältst du die Kontrolle über deine öffentliche API.
Wenn du einfach nur eine App baust und kein Package veröffentlichst, ist von den dreien realistisch nur type für dich relevant.
Eine realistische package.json
Alles zusammengenommen sieht die package.json einer kleinen Node-App in der Praxis ungefähr so aus:
Beachte engines.node. Das ist nur ein Hinweis – npm gibt eine Warnung aus (oder bricht mit engine-strict ab), wenn die Node-Version des Nutzers nicht passt. Gute Praxis für alles, was du veröffentlichen willst.
Weitere nützliche Felder
Ein paar Felder, die dir immer wieder begegnen werden:
private: true– verhindert, dass du das Paket versehentlich auf npm veröffentlichst. Setz das bei jedem Projekt, das nicht publiziert werden soll.license– ein SPDX-Kürzel wie"MIT"oder"ISC". Relevant bei allem, was öffentlich ist.repository,bugs,homepage– tauchen auf der Registry-Seite bei npm auf.bin– wenn dein Paket ein CLI mitbringt, mappst du hier Befehlsnamen auf Skriptdateien. Nach der Installation sind diese Befehle direkt ausführbar.workspaces– für Monorepos; sagt npm, dass es Unterverzeichnisse als verlinkte Pakete behandeln soll.
Du brauchst nicht alle davon – nur die, die zu deinem Projekt passen.
Typische Stolperfallen
Ein paar Dinge, über die viele stolpern:
node_moduleseinchecken. Lass es. Pack den Ordner in die.gitignore. Mitpackage.jsonpluspackage-lock.jsonkann jeder das Ganze pernpm installwiederherstellen.package-lock.jsonnicht einchecken. Die Lockfile gehört ins Repo. Ohne sie wird „läuft bei mir" schnell zum echten Problem, weil semver-Ranges im Laufe der Zeit unterschiedliche Versionen auflösen können.- Runtime-Abhängigkeiten in
devDependenciesstecken. Lokal läuft alles, weil Dev-Dependencies mitinstalliert werden – in Produktion werden sie übersprungen und es knallt. Wenn der ausgelieferte Code eine Library benutzt, gehört sie independencies. - Versionen von Hand ändern, ohne neu zu installieren. Wenn du eine Version in
package.jsonanpasst, führ auchnpm installaus – sonst laufennode_modulesund die Lockfile auseinander.
Weiter geht's: die Node-Runtime
package.json beschreibt Node gegenüber, was dein Projekt ist. Wie es tatsächlich läuft, entscheidet die Node-Runtime – Modulauflösung, Built-in-Module, Globals und die Event-Loop unter der Haube. Genau darum geht es auf der nächsten Seite.
Häufig gestellte Fragen
Wofür braucht man die package.json überhaupt?
Sie ist die Manifest-Datei eines Node.js-Projekts. Drin stehen Name und Version des Projekts, welche Pakete es braucht, welche Scripts du per npm run ausführen kannst und Metadaten wie der Entry Point oder der Modultyp. npm install liest diese Datei und weiß dadurch, was heruntergeladen werden muss.
Was ist der Unterschied zwischen dependencies und devDependencies?
dependencies sind Pakete, die dein Code zur Laufzeit braucht – also z. B. express oder react. devDependencies brauchst du nur während der Entwicklung oder beim Build: Test-Runner, Bundler, Linter. Wenn jemand dein Paket als Abhängigkeit installiert, überspringt npm deine devDependencies automatisch.
Was bedeuten ^ und ~ bei den Versionen in der package.json?
Das sind Semver-Range-Operatoren. ^1.2.3 erlaubt jede 1.x.x-Version ab 1.2.3 (gleiche Major-Version). ~1.2.3 ist strenger und erlaubt nur 1.2.x ab 1.2.3 (gleiche Minor-Version). Steht 1.2.3 ohne Präfix da, wird genau diese Version gepinnt. Die package-lock.json hält zusätzlich die tatsächlich aufgelösten Versionen fest, damit Installationen reproduzierbar bleiben.
Wie erstelle ich eine package.json?
Führe npm init in einem leeren Ordner aus und beantworte die Fragen – oder nimm npm init -y, dann werden die Defaults übernommen und die Datei ist sofort da. Du kannst sie aber auch einfach von Hand anlegen, es ist ja nur JSON. Wirklich Pflicht sind nur die Felder name und version.