Zum Inhalt springen

2.1 npm: Projekt und Abhängigkeiten

Zu Zen-Modus wechseln

Nach dieser Einheit kannst du:

  • ein Node.js-Projekt mit npm init anlegen und Skripte in der package.json definieren und ausführen.
  • Pakete als dependencies oder devDependencies installieren und die Wahl mit der Prüffrage „Braucht das laufende Programm dieses Paket?“ begründen.
  • begründen, warum package.json und Sperrdatei ins Repository gehören und node_modules/ nicht.

Wer eine Wohnung einrichtet, baut den Kasten nicht selbst, sondern holt ihn aus dem Möbelhaus - samt Bauanleitung, Schraubenbeutel und der Hoffnung, dass kein Teil fehlt. Mit Software ist es genauso: Kaum jemand schreibt Datumsrechnung oder Eingabeprüfung selbst. Kapitel 2 zeigt in zwei Einheiten, wie man fertige Teile holt, ohne sich dabei Chaos ins Projekt zu tragen. Diese erste Einheit kümmert sich um die Einkaufsliste und darum, was ins Projekt kommt.

Mit der Einkaufsliste wird im Möbelhaus bestellt. Der Lieferdienst holt den Kasten samt Bauanleitung und Schraubenbeutel aus dem Lager und bringt ihn in die Wohnung. Dazu gibt es einen Lieferschein mit der genauen Artikelnummer jedes Teils
Abb. 2.1: Einkaufsliste, Lager, Lieferung und Lieferschein: das Bild, das sich durch Kapitel 2 zieht (eigene Darstellung).

JavaScript bringt von Haus aus keine umfangreiche Standardbibliothek mit: kein Werkzeug für Datumsrechnung mit Zeitzonen, keine Validierung, kein Testframework, keinen Datenbankzugriff. Was andere Sprachen mitliefern, kommt hier aus dem Ökosystem. Der Zugang dazu ist npm, der Paketmanager von Node.js.

Das hat zwei Seiten. Auf der einen steht die größte Sammlung wiederverwendbarer Software, die es gibt; kaum ein Problem, für das nicht bereits ein Paket existiert. Auf der anderen steht die Verantwortung für fremden Code, den niemand im Team gelesen hat, der eigene Abhängigkeiten mitbringt und der irgendwann nicht mehr gepflegt wird. Kapitel 2 behandelt beides: in dieser Einheit die Werkzeuge, in Einheit 2.2 Versionen und das Urteilsvermögen im Umgang mit fremdem Code.

  • Paket (package): ein veröffentlichter, wiederverwendbarer Baustein aus fremdem Code, etwa ein Validierer oder ein Testframework.
  • Registry: das zentrale Verzeichnis, aus dem Pakete geladen werden; für JavaScript die npm-Registry auf npmjs.com.
  • Paketmanager (package manager): das Kommandozeilenwerkzeug, das Pakete aus der Registry holt, installiert und ihre Versionen verwaltet, hier npm oder pnpm.
  • Abhängigkeit (dependency): ein Paket, das dein Projekt braucht. Pakete haben selbst wieder Abhängigkeiten, man spricht dann von transitiven Abhängigkeiten.
  • Sperrdatei (lock file): hält fest, welche Version jedes Pakets tatsächlich installiert wurde (package-lock.json bei npm, pnpm-lock.yaml bei pnpm).

Im Bild vom Möbelhaus: Die Registry ist das Lager, der Paketmanager der Lieferdienst, und die package.json aus dem nächsten Abschnitt ist deine Einkaufsliste.

npm install liest die package.json und lädt Pakete aus der npm-Registry. Die Pakete landen in node_modules, die exakten Versionen in der package-lock.json. package.json und package-lock.json gehören ins Repository, node_modules nicht. Dazu die Begriffe der Analogie: Lager, Einkaufsliste, Lieferdienst, Lieferung, Lieferschein
Abb. 2.2: Was bei npm install passiert, mit den Begriffen aus dem Möbelhaus (eigene Darstellung).

Ein JavaScript-Projekt beginnt mit einem Befehl:

Terminal-Fenster
npm init -y # -y accepts all defaults
pnpm init # pnpm: no prompts, so no -y needed

Das Ergebnis ist eine einzige Datei, package.json. Sie beschreibt das Projekt, und zwar maschinenlesbar. Genau deshalb kann ein fremder Rechner aus ihr die Arbeitsumgebung wiederherstellen.

{
"name": "buffet-app",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "node --watch src/main.js",
"test": "vitest run",
"typecheck": "tsc --noEmit",
"check": "biome check ."
},
"dependencies": {
"zod": "^4.4.3"
},
"devDependencies": {
"@biomejs/biome": "2.5.15",
"typescript": "^7.0.2",
"vitest": "^4.1.10"
}
}

Beispielprojekt: 02_npm_pakete_und_projekte_examples/ex01_buffet_app

Die Felder im Einzelnen:

FeldBedeutung
name, versionKennung des Projekts; Pflicht, sobald ein Paket veröffentlicht wird
type"module" legt fest, dass .js-Dateien Module mit import/export sind (siehe Kapitel 1)
scriptsbenannte Befehle, aufrufbar mit npm run <name> (pnpm: pnpm run <name>)
dependenciesPakete, die das laufende Programm braucht
devDependenciesPakete, die nur beim Entwickeln gebraucht werden

Ein frisches npm init -y trägt bei type den Wert "commonjs" ein (getestet mit npm 11.10.1). Weil wir mit import und export arbeiten, änderst du ihn als Erstes auf "module"; sonst bricht Node beim ersten import mit Cannot use import statement outside a module ab.

Zurück zum Möbelhaus: Die package.json ist die Einkaufsliste. Sie sagt, was das Projekt braucht, aber nicht, welches Exemplar tatsächlich geliefert wurde. Das hält erst die Sperrdatei fest (Einheit 2.2, Abschnitt “Versionen”).

Ausschnitt der package.json des buffet-app mit drei markierten Bereichen: scripts enthält die Befehle des Projekts, aufgerufen mit npm run; dependencies wird im Betrieb gebraucht, im Möbelhaus-Bild die Schrauben; devDependencies nur beim Entwickeln, der Akkuschrauber
Abb. 2.3: Die package.json als Einkaufsliste des Projekts (eigene Darstellung).

scripts ist das am meisten unterschätzte Feld. Es hält fest, wie das Projekt gestartet, getestet und geprüft wird, und zwar so, dass es niemand im Wiki nachschlagen muss:

Terminal-Fenster
npm run dev
npm run typecheck
npm run check
npm test # test and start work without "run"
pnpm run dev
pnpm typecheck # pnpm: "run" is optional for every script name
pnpm test # that does not clash with a built-in pnpm command

Ein neues Teammitglied klont das Repository, führt npm install (pnpm: pnpm install) aus und liest in scripts, welche Befehle es gibt. Das ist gelebte Projektdokumentation an der einzigen Stelle, an der sie nicht veraltet: Ein falscher Eintrag fällt beim nächsten Aufruf sofort auf.

Wo begegnet dir das? Bei jedem Projekt, das du ab jetzt klonst, ob von Mitschüler:innen, aus einer Übungsvorlage oder aus einem Open-Source-Repository: Der erste Blick gilt scripts. Dort steht, ob das Projekt mit npm run dev, npm start oder etwas ganz anderem startet. Ab Kapitel 7 legen die Projektgeneratoren von Vite und Next.js diese Skripte für dich an.

Auflösung

npm test und npm run check. Für test (und start) kennt npm eine Abkürzung ohne run. npm check und npm typecheck sind keine npm-Befehle: npm meldet Unknown command und schlägt npm run check beziehungsweise npm run typecheck vor (getestet mit npm 11.10.1). Bei pnpm funktioniert pnpm check dagegen (getestet mit pnpm 12.5.1).

Terminal-Fenster
npm install zod # into dependencies
npm install --save-dev vitest # into devDependencies (short: -D)
npm install # restore everything from package.json
npm uninstall zod # remove
pnpm add zod # into dependencies
pnpm add --save-dev vitest # into devDependencies (short: -D)
pnpm install # restore everything from package.json
pnpm remove zod # remove

Jede Installation tut drei Dinge:

  1. Sie lädt das Paket in den Ordner node_modules/.
  2. Sie trägt es in die package.json ein.
  3. Sie hält die exakt installierte Version in der package-lock.json fest (pnpm: pnpm-lock.yaml).

Pakete haben selbst Abhängigkeiten, und diese wieder. Im buffet-app stehen vier Pakete in der package.json. Ein npm install holt daraus 49 Pakete in rund 160 Ordnern mit knapp 150 MB (gemessen unter Windows im Oktober 2026).

Das buffet-app hat vier direkte Abhängigkeiten. zod hat keine eigenen Abhängigkeiten. typescript und @biomejs/biome holen je eine zum Betriebssystem passende Variante, etwa @typescript/typescript-win32-x64 und @biomejs/cli-win32-x64. vitest holt vite, das wieder rolldown, postcss und weitere Pakete braucht, dazu @vitest/expect und weitere. Aus 4 Einträgen werden 49 Pakete
Abb. 2.4: Vier Einträge in der package.json ziehen einen ganzen Abhängigkeitsbaum nach (eigene Darstellung nach npm ls --all).

Das ist normal und kein Grund zur Sorge. Weil node_modules/ jederzeit aus package.json und Sperrdatei wiederhergestellt werden kann, gehört der Ordner aber nie ins Repository. pnpm legt die Dateien nur einmal in einem zentralen Speicher auf dem Rechner ab und verknüpft sie per Links; das spart Plattenplatz, ändert aber nichts an der Regel.

Die Unterscheidung ist keine Formsache. Die Prüffrage lautet: Braucht das fertige, laufende Programm dieses Paket? Im Möbelhaus-Bild gehören die Schrauben zum Kasten und bleiben drin, der Akkuschrauber dagegen wird nur zum Aufbauen gebraucht.

dependenciesdevDependencies
Gebrauchtim Betrieb des Programmsbeim Entwickeln, Testen und Bauen
Im Möbelhaus-Bilddie Schraubender Akkuschrauber
Beispiele im buffet-appzod (prüft bei jeder Bestellung die Eingaben)typescript, vitest, @biomejs/biome
Installieren mitnpm install <paket>npm install --save-dev <paket>

Auf dem Server läuft am Ende übersetztes JavaScript, kein Compiler und kein Testframework. Wer alles in dependencies wirft, schleppt beim Betrieb ein Vielfaches an Code mit, und jede Zeile davon ist eine mögliche Sicherheitslücke.

Wo begegnet dir das? Auf deinem eigenen Rechner installiert npm install beide Listen, denn zum Entwickeln brauchst du beides. Wird das Projekt später auf einem Server eingespielt, lässt npm ci --omit=dev die devDependencies weg. Genau dort zahlt sich die saubere Trennung aus.

Links dein Rechner: npm install installiert die dependencies, im buffet-app zod, und die devDependencies typescript, vitest und @biomejs/biome. Rechts der Server: npm ci --omit=dev installiert nur zod, keine Werkzeuge, weniger Code, weniger Angriffsfläche
Abb. 2.5: Am eigenen Rechner kommen beide Listen, am Server nur die dependencies (eigene Darstellung).

Biome kennst du aus Kapitel 1: Linter und Formatierer in einem Werkzeug. Im buffet-app ist es die typische devDependency, denn geprüft und formatiert wird beim Entwickeln, nicht im Betrieb. Eingerichtet wurde es so:

Terminal-Fenster
npm install --save-dev --save-exact @biomejs/biome # exact version, no ^
npx @biomejs/biome init # creates biome.json
pnpm add --save-dev --save-exact @biomejs/biome
pnpm exec biome init

Danach steht "@biomejs/biome": "2.5.15" unter devDependencies, ohne ^ davor (warum, zeigt Einheit 2.2 im Abschnitt “Versionen”). Das Skript "check": "biome check ." wurde von Hand ergänzt. npm run check prüft damit das ganze Projekt auf einen Schlag, und alle im Team verwenden dieselbe Biome-Version.

Eine Einstellung musst du kennen: biome init stellt die Einrückung auf Tabs. Das buffet-app rückt mit Leerzeichen ein, deshalb steht in seiner biome.json unter formatter der Eintrag "indentStyle": "space". Ohne ihn meldet npm run check jede Datei als Formatfehler.

  1. package.json - ja. Sie ist die Projektbeschreibung.

  2. package-lock.json (pnpm: pnpm-lock.yaml) - ja. Sie ist der Grund, warum bei allen dasselbe installiert wird.

  3. node_modules/ - nein. Der Ordner ist reproduzierbar, riesig und plattformabhängig; er gehört in die .gitignore.

Damit ist jedes geklonte Projekt mit einem einzigen npm install (pnpm: pnpm install) lauffähig.

Auflösung

react und date-fns laufen mit, wenn jemand die Anwendung benutzt: dependencies. vitest und @biomejs/biome braucht nur, wer am Code arbeitet: devDependencies. Die Prüffrage ist immer dieselbe: Läuft der Code dieses Pakets, wenn ein Benutzer die App verwendet?

  1. node_modules/ eingecheckt: bläht das Repository um Zehntausende Dateien auf. In die .gitignore, bevor der erste Commit passiert.
  2. Alles in dependencies: Testframework und Compiler landen im Betrieb; unnötiger Code und unnötige Angriffsfläche.
  3. Fehlendes "type": "module": Node 24 führt die Datei trotzdem aus, warnt aber bei jedem Start mit MODULE_TYPELESS_PACKAGE_JSON. Steht dort "type": "commonjs", bricht import mit Cannot use import statement outside a module ab.
  4. Biome meldet jede Datei als Formatfehler: biome init stellt die Einrückung auf Tabs, dein Code rückt mit Leerzeichen ein. Entweder in der biome.json "indentStyle": "space" setzen oder einmal npx @biomejs/biome check --write . laufen lassen.
  • npm Docs - die offizielle Referenz zu allen Befehlen; besonders lesenswert ist die Seite zu npm install.
  • npm-Dokumentation: package.json - alle Felder mit Erklärung, auch die, die erst beim Veröffentlichen eigener Pakete wichtig werden.
  • npmjs.com - die Paketsuche selbst: Hier siehst du zu jedem Paket Version, Abhängigkeiten und Installationsbefehl.
  • pnpm-Dokumentation - Befehlsreferenz und Begründung für pnpm; hilfreich, wenn du statt npm damit arbeitest.
  • Biome: Configuration - alle Einstellungen der biome.json, darunter indentStyle.

Du kannst dir diese Einheit kostenlos mit der Vorlesefunktion deines Browsers oder Betriebssystems vorlesen lassen (etwa „Laut vorlesen” in Edge), ohne Konto und ohne Upload. Freiwillig kannst du mit Google NotebookLM (Google-Konto nötig) aus diesem öffentlichen Text eine gesprochene Zusammenfassung erzeugen, auch auf Deutsch.

Du kannst jetzt:

  • ein Node.js-Projekt mit npm init (pnpm: pnpm init) anlegen und Skripte in der package.json definieren und ausführen.
  • Pakete als dependencies oder devDependencies installieren und die Wahl mit der Prüffrage „Braucht das laufende Programm dieses Paket?“ begründen.
  • begründen, warum package.json und Sperrdatei ins Repository gehören und node_modules/ nicht.
  • Aufgabe 01 - TypeScript-Spickzettel, Stufe Wesentlich_überwiegend (Projekt anlegen, package.json, Skripte, devDependencies); Sperrdatei und npm ci folgen in Einheit 2.2
  • package.json: alle Felder → npm-Dokumentation: package.json
  • npm install, Option --omit=dev → npm-Dokumentation: npm install
  • npm init -y trägt "type": "commonjs" ein; npm check ist kein npm-Befehl, pnpm check startet das Skript → eigener Test mit npm 11.10.1 und pnpm 12.5.1 (Oktober 2026)
  • Verhalten von Node ohne "type": "module" und mit "type": "commonjs" → eigener Test mit Node.js 24.11.1 (Oktober 2026); Node.js-Dokumentation: Syntax detection
  • npm-Befehle allgemein → npm Docs
  • npm-Registry, Paketsuche → npmjs.com
  • pnpm-Befehle → pnpm-Dokumentation
  • Biome als devDependency, --save-exact, biome init → Biome: Getting Started
  • biome.json, indentStyle → Biome: Configuration
  • Paketzahl, Ordnerzahl und Größe von node_modules/ sowie der Abhängigkeitsbaum im buffet-app → eigene Messung mit npm install und npm ls --all (Node.js 24.11.1, Windows, Oktober 2026)
  • Abb. 2.1 (Möbelhaus), Abb. 2.2 (Was bei npm install passiert), Abb. 2.3 (package.json), Abb. 2.4 (Abhängigkeitsbaum), Abb. 2.5 (Rechner und Server) → eigene Darstellung
  • Oliver Maurhart (MAO)
  • Michael Gabriel (GAM)

CC-BY-SA

https://qiz.at/rules