Zum Inhalt springen
aviral gupta

// B1.1 · ca. 32 Min. · Einstieg

Node.js-Projektgrundlagen: Module, Konfiguration und Fehler

Nach dieser Lektion richten Sie ein kleines Node.js-Projekt ein, das im beabsichtigten Modulsystem läuft, seine Konfiguration ohne Typfehler aus einer .env-Datei liest und mit einer klaren Meldung und einem Exit-Code ungleich 0 scheitert.

Lektion 1 von 5 in B1 Node ausführen und Projektgrundlagen

Anfang des Moduls

Danach können Sie

  • An Dateiendung und "type" in package.json entscheiden, ob eine Datei ein ES-Modul oder CommonJS ist
  • Einstellungen mit --env-file laden und Strings aus process.env in Zahlen und Wahrheitswerte umwandeln
  • Asynchrone Dateifehler mit try/catch abfangen und process.exitCode setzen
  1. Aufwärmen · Aufgabe 1 von 7

    Aufwärmen: Die package.json eines Projekts enthält das Skript unten. Welcher Befehl führt es im Projektordner aus?

    {
      "scripts": {
        "start": "node src/server.js"
      }
    }
  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie es vorher, bevor Sie weiterlesen. Sie führen node --env-file=.env app.mjs mit der .env-Datei und dem Skript unten aus. Was gibt es aus?

    // .env
    // PORT=3000
    // DEBUG=false
    
    // app.mjs
    const port = process.env.PORT;
    console.log(port + 1);
    console.log(process.env.DEBUG ? 'debug on' : 'debug off');
  3. Üben · Aufgabe 3 von 7

    Ordnen Sie jeder Datei zu, wie Node.js sie lädt und warum.

  4. Üben · Aufgabe 4 von 7

    Finden Sie den Fehler. package.json hat "type": "module". node index.js scheitert mit „ReferenceError: require is not defined in ES module scope“. Welche Änderungen beheben ihn? Wählen Sie alle zutreffenden.

    // index.js
    const fs = require('node:fs');
    console.log(typeof fs.readFileSync);

    Wählen Sie alle zutreffenden aus.

  5. Üben · Aufgabe 5 von 7

    Vervollständigen Sie den Entwicklungsbefehl, damit Node.js .env lädt und automatisch neu startet, sobald sich server.js oder ein Modul, das es importiert, ändert. Tippen Sie das fehlende Flag.

    node --env-file=.env server.js
  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. missing.json existiert nicht. Die Funktion fängt Fehler ab und weicht auf "{}" aus. Was passiert, wenn Sie node teaser.mjs ausführen?

    // teaser.mjs
    import { readFile } from 'node:fs/promises';
    
    async function loadConfig() {
      try {
        return readFile('missing.json', 'utf8');
      } catch {
        return '{}';
      }
    }
    
    console.log(await loadConfig());
  7. Anwenden · Aufgabe 7 von 7

    Mini-Aufgabe. Bauen Sie einen kleinen Konfigurationslader als ES-Modul-Projekt: (1) eine package.json mit "type" und einem "dev"-Skript, das --watch und --env-file-if-exists nutzt; (2) src/config.js, das PORT in eine geprüfte Zahl mit Standardwert 3000 und DEBUG in einen Wahrheitswert umwandelt und eine optionale features.json mit fs/promises liest; (3) src/server.js, das die Konfiguration ausgibt oder eine klare Fehlermeldung ausgibt und den Exit-Code 1 setzt. Testen Sie ohne .env, mit PORT=abc und mit einer kaputten features.json.

    Prüfen Sie Ihr Ergebnis anhand dieser Liste

Selbst programmieren

Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.

Ausgearbeitetes Beispiel

Konfiguration aus .env, umgewandelt, mit abgefangener fehlender Datei

Dieses Programm lädt .env selbst mit process.loadEnvFile, das tut, was node --env-file=.env main.js täte, und läuft deshalb mit einfachem node main.js. Die erste Zeile zeigt die Falle: PORT kommt als String an, also hängt + 1 nur an, und der String "false" ist truthy. Dann wandelt es jeden Wert einmal um, am Rand des Programms, und liest eine optionale features.json mit dem await im try, sodass die fehlende Datei im catch landet und das Programm auf {} ausweicht.

main.js

import {loadEnvFile} from 'node:process';
import {readFile} from 'node:fs/promises';

// Does what node --env-file=.env main.js does: each line becomes a string in process.env.
loadEnvFile('.env');
console.log(typeof process.env.PORT, process.env.PORT + 1, Boolean(process.env.DEBUG));

// Convert once, at the edge of the program.
const port = Number(process.env.PORT);
const debug = process.env.DEBUG === 'true';
console.log({port, debug});

// Await inside try, so a missing file lands in catch.
let features = {};
try {
  features = JSON.parse(await readFile('features.json', 'utf8'));
} catch (error) {
  if (error.code !== 'ENOENT') throw error;
  console.log('features.json:', error.code, '- using {}');
}
console.log({port, debug, features});

.env

PORT=8080
DEBUG=false

Ausführen mit

node main.js

Ausgabe

string 80801 true
{ port: 8080, debug: false }
features.json: ENOENT - using {}
{ port: 8080, debug: false, features: {} }
  • typeof process.env.PORT ist string, also ergibt PORT + 1 den Wert 80801, und Boolean("false") ist true.
  • Number() und === 'true' machen aus den Strings die Zahl 8080 und den Wahrheitswert false.
  • Die fehlende features.json wird abgefangen, weil das await im try steht. Ihr error.code ist ENOENT.
  • Jeder andere Fehler, etwa kaputtes JSON, wird erneut ausgelöst: Eine fehlende optionale Datei ist in Ordnung, eine kaputte nicht.

Übungen

Übung 1 von 2

Strings aus der Umgebung in eine Konfiguration umwandeln

Schreiben Sie readConfig(env). env ist ein Objekt aus Strings, wie process.env nach node --env-file=.env. Geben Sie {port, debug} zurück: port ist PORT als Zahl, 3000, wenn PORT nicht gesetzt ist; debug ist nur dann true, wenn DEBUG genau "true" ist. Werfen Sie einen Error, wenn PORT keine ganze Zahl von 1 bis 65535 ist. In einem echten Programm rufen Sie readConfig(process.env) auf; die Tests übergeben stattdessen einfache Objekte, deshalb läuft das auch im Browser.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser den JavaScript-Runner herunter (bis zu 0.1 MB) und speichert ihn im Cache. Ihr Code läuft in der eigenen Engine Ihres Browsers und bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    env.PORT ?? '3000' nimmt 3000 nur, wenn PORT gar nicht gesetzt ist.

  2. Hinweis 2

    Number("abc") ist NaN, und das ist keine ganze Zahl: Number.isInteger(port) erkennt es.

  3. Hinweis 3

    Vergleichen Sie den String: env.DEBUG === 'true'. Alles andere, auch "false", ergibt false.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

// env is an object of strings, like process.env.
export function readConfig(env) {
  const port = Number(env.PORT ?? '3000');
  if (!Number.isInteger(port) || port < 1 || port > 65535) {
    throw new Error('PORT must be a whole number from 1 to 65535, got "' + env.PORT + '"');
  }
  return {port, debug: env.DEBUG === 'true'};
}

console.log(readConfig({PORT: '8080', DEBUG: 'false'}));
Auf dem eigenen Computer ausführen

Installieren Sie Node.js 24 LTS oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.js

// env is an object of strings, like process.env.
export function readConfig(env) {
  return {port: env.PORT, debug: env.DEBUG};
}

console.log(readConfig({PORT: '8080', DEBUG: 'false'}));

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {readConfig} from './main.js';

test('PORT="8080" wird zur Zahl 8080', () => {
  const got = readConfig({PORT: '8080'}).port;
  assert.equal(got, 8080, `port war ${JSON.stringify(got)}`);
});

test('DEBUG="false" ergibt false und DEBUG="true" ergibt true', () => {
  assert.equal(readConfig({DEBUG: 'false'}).debug, false, 'DEBUG="false" sollte den Wahrheitswert false ergeben');
  assert.equal(readConfig({DEBUG: 'true'}).debug, true, 'DEBUG="true" sollte den Wahrheitswert true ergeben');
});

test('ohne PORT ist port 3000', () => {
  const got = readConfig({}).port;
  assert.equal(got, 3000, `port war ${JSON.stringify(got)}`);
});

test('PORT="abc" oder "70000" wirft einen Error', () => {
  assert.throws(() => readConfig({PORT: 'abc'}), Error, 'PORT="abc" sollte werfen');
  assert.throws(() => readConfig({PORT: '70000'}), Error, 'PORT="70000" sollte werfen');
});

package.json

{
  "type": "module"
}

package.json sagt Node.js, dass die .js-Dateien Module sind; lassen Sie die Datei im Ordner.

Programm ausführen:

node main.js

Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):

node --test
learnrun.js herunterladen

Übung 2 von 2

Eine optionale JSON-Datei lesen

Schreiben Sie loadJson(path, fallback) mit readFile aus node:fs/promises. Die Funktion liefert das geparste JSON der Datei. Existiert die Datei nicht (error.code ist ENOENT), liefert sie stattdessen fallback. Jeder andere Fehler, etwa kaputtes JSON, muss weiterhin geworfen werden. Die letzten Zeilen von main.js fangen diesen Fehler schon ab, geben ihn aus und setzen process.exitCode = 1. Starten Sie es mit node main.js und prüfen Sie es mit node --test.

Diese Übung braucht Node.js auf Ihrem Computer (die Browser-Version kann sie nicht ausführen). Dateien und Befehle stehen unten.

Hinweise
  1. Hinweis 1

    Setzen Sie nur den Aufruf von readFile in try, mit await, damit seine Ablehnung in Ihrem catch landet.

  2. Hinweis 2

    Prüfen Sie im catch error.code === 'ENOENT' und geben Sie fallback zurück; sonst werfen Sie error erneut.

  3. Hinweis 3

    Parsen Sie nach dem try: Dann wird ein SyntaxError aus JSON.parse nicht für eine fehlende Datei gehalten.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

import {readFile} from 'node:fs/promises';

// The parsed JSON in the file, or fallback when the file does not exist.
export async function loadJson(path, fallback) {
  let text;
  try {
    text = await readFile(path, 'utf8');
  } catch (error) {
    if (error.code === 'ENOENT') return fallback;
    throw error;
  }
  return JSON.parse(text);
}

try {
  console.log(await loadJson('settings.json', {}));
} catch (error) {
  console.error('Cannot read settings:', error.message);
  process.exitCode = 1;
}
Auf dem eigenen Computer ausführen

Installieren Sie Node.js 24 LTS oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.js

import {readFile} from 'node:fs/promises';

// The parsed JSON in the file, or fallback when the file does not exist.
export async function loadJson(path, fallback) {
  const text = await readFile(path, 'utf8');
  return JSON.parse(text);
}

try {
  console.log(await loadJson('settings.json', {}));
} catch (error) {
  console.error('Cannot read settings:', error.message);
  process.exitCode = 1;
}

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {loadJson} from './main.js';

test('liest und parst settings.json', async () => {
  const got = await loadJson('settings.json', {});
  assert.deepEqual(got, {theme: 'dark'}, `loadJson("settings.json") lieferte ${JSON.stringify(got)}`);
});

test('eine fehlende Datei ergibt den fallback', async () => {
  const got = await loadJson('missing.json', {theme: 'light'});
  assert.deepEqual(got, {theme: 'light'}, `loadJson("missing.json", fallback) lieferte ${JSON.stringify(got)}`);
});

test('kaputtes JSON wirft weiterhin einen SyntaxError', async () => {
  await assert.rejects(loadJson('broken.json', {}), SyntaxError, 'loadJson("broken.json") sollte mit einem SyntaxError scheitern, nicht fallback liefern');
});

settings.json

{"theme": "dark"}

broken.json

{"theme": }

package.json

{
  "type": "module"
}

package.json sagt Node.js, dass die .js-Dateien Module sind; lassen Sie die Datei im Ordner.

Programm ausführen:

node main.js

Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):

node --test
learnrun.js herunterladen

Häufige Fehler

require in einem ES-Modul

const fs = require('node:fs');
console.log(typeof fs.readFileSync);

Was Node.js ausgibt

ReferenceError: require is not defined in ES module scope, you can use import instead

Warum, und die Lösung

Die package.json des Projekts sagt "type": "module", also ist main.js ein ES-Modul, und ES-Module haben kein require. Schreiben Sie stattdessen import fs from 'node:fs'; oder benennen Sie eine Datei, die CommonJS bleiben muss, in .cjs um.

__dirname in einem ES-Modul

import {join} from 'node:path';
console.log(join(__dirname, 'config.json'));

Was Node.js ausgibt

ReferenceError: __dirname is not defined in ES module scope

Warum, und die Lösung

__dirname und __filename gehören zu CommonJS; ES-Module haben sie nicht. Nehmen Sie import.meta.dirname und import.meta.filename, die seit v24.0.0 stabil sind: join(import.meta.dirname, 'config.json').

return ohne await im try

import {readFile} from 'node:fs/promises';

async function loadConfig() {
  try {
    return readFile('config.json', 'utf8');
  } catch {
    return '{}';
  }
}

console.log(await loadConfig());

Was Node.js ausgibt

Error: ENOENT: no such file or directory, open 'config.json'

Warum, und die Lösung

return reicht das Promise aus dem try hinaus, bevor es abgelehnt wird, also sieht der catch-Block den Fehler nie, und die Ablehnung beendet das Programm mit Exit-Code 1. Schreiben Sie return await readFile(...): Dann wird die Ablehnung im try ausgelöst, und der catch-Block liefert '{}'.

JavaScript im Browser: die eigene Engine Ihres Browsers in einem abgeschotteten Worker. Syntaxfehler findet acorn 8.18.0, MIT. Lizenz und Quellcode

Abschlussquiz

5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.

Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.

Problem melden

Etwas ist falsch oder unklar? Beschreiben Sie es kurz, dann wird es geprüft und korrigiert.

#

Mindestens 20 Zeichen.

Nur, wenn Sie eine Antwort wünschen.

Kernideen

Endung und "type" entscheiden über das Modulsystem

Eine .mjs-Datei ist immer ein ES-Modul und eine .cjs-Datei immer CommonJS, egal was in package.json steht. Eine .js-Datei folgt dem Feld "type" der nächstgelegenen übergeordneten package.json: "module" heißt ES-Modul, "commonjs" heißt CommonJS. Ohne das Feld führt Node.js sie als CommonJS aus, es sei denn, es findet ES-Modul-Syntax wie import; dann führt es die Datei erneut als ES-Modul aus und warnt. Die Doku rät Paketautoren, "type" immer anzugeben. ES-Module verwenden import und haben kein require, __dirname oder module.exports; import.meta.dirname ersetzt __dirname. Eingebaute Module lassen sich als node:fs importieren, einige wie node:test und node:sqlite gibt es nur mit dem Präfix.

Konfiguration kommt als String an

node --env-file=.env app.js liest Zeilen der Form KEY=value in process.env. Das Flag ist seit v24.10.0 stabil, ebenso process.loadEnvFile('.env'), das dasselbe aus dem Programm heraus tut. Ist eine Variable in der echten Umgebung schon gesetzt, gewinnt dieser Wert. Eine fehlende Datei ist ein Fehler; --env-file-if-exists überspringt sie stattdessen. Jeder Wert in process.env ist ein String, also ist "false" truthy und "3000" + 1 ergibt "30001". Wandeln Sie mit Number() um, vergleichen Sie Wahrheitswerte mit === 'true' und prüfen Sie die Werte, bevor Sie sie verwenden. node --watch startet den Prozess neu, wenn sich die Einstiegsdatei oder etwas, das sie importiert, ändert.

Asynchrone Fehler fängt nur ab, wer auf sie wartet

Setzen Sie await-Aufrufe mit fs/promises in try/catch. try/catch sieht nur eine Ablehnung, auf die Sie darin mit await warten: return promise ohne await verlässt den try-Block, bevor das Promise scheitert. Eine Ablehnung, die niemand behandelt, beendet den Prozess standardmäßig mit Exit-Code 1, weil der Standardmodus von --unhandled-rejections throw ist. Um einen Fehler zu melden und Aufräumarbeiten trotzdem laufen zu lassen, setzen Sie process.exitCode = 1, statt process.exit() aufzurufen. Exit-Code 0 heißt Erfolg, 1 ein nicht abgefangener Fehler, 9 ein ungültiges Argument, etwa eine fehlende --env-file.

Quellen

Zuletzt geprüft am 30. September 2026