mitmario.dev

Konfiguration

Node.js Sandbox 3 Min Lesezeit 3 BeispieleLektion 2 von 7

process.env hast du in Lektion 4.2 kennengelernt und seitdem an einem Dutzend Stellen benutzt. Diese Lektion räumt damit auf, und zwar aus einem Grund, der nichts mit Ordnung zu tun hat: ein Tippfehler in einem Variablennamen ist kein Fehler, sondern undefined.

Der Zettel, den niemand hat

Acht Zeilen mit process.env
process.env.SITZUNGSDAUER_MINUTEN = "120";
process.env.MAX_KOERPER_KB = "500";
process.env.DEBUG = "false";

await import("./server.js");
await import("./sitzung.js");

// Und der Klassiker aus Lektion 4.2 gleich mit:
console.log("DEBUG greift:", process.env.DEBUG ? "ja" : "nein");

Zwei Dinge gehen hier schief, und beide sind still. Der Name in sitzung.js ist falsch geschrieben, also greift der Standardwert, obwohl die Variable gesetzt ist. Und MAX_KOERPER_KB kommt als Zeichenkette an, denn aus der Umgebung kommt immer Text, auch wenn dort eine Zahl steht. Beides fällt erst auf, wenn jemand sich wundert, warum eine Einstellung nichts bewirkt.

Dazu kommt eine Frage, die man in so einem Projekt nicht beantworten kann: welche Variablen braucht diese Anwendung eigentlich? Die einzige ehrliche Antwort wäre grep -r process.env. Probier es aus, grep liegt in der Sandbox. Starte das Beispiel und tipp im Terminal:

grep -rn process.env .

Acht Zeilen aus drei Dateien, und schon bei acht musst du zweimal hinsehen, welche davon wirklich etwas lesen: Drei setzen in probe.js nur die Umgebung, damit das Beispiel überhaupt etwas zu zeigen hat. In einem gewachsenen Projekt sind es dreißig, verteilt über zwanzig Dateien.

Und die eigentliche Schwäche der Antwort steht schon in ihr drin: grep findet nur, was jemand schon geschrieben hat, nicht das, was fehlt.

Ein Modul, das liest, umwandelt und prüft

Alles an einer Stelle
process.env.SITZUNGS_GEHEIMNIS = "aus-der-umgebung";
process.env.MAX_KOERPER_KB = "500";
process.env.DEBUG = "false";

const { konfig } = await import("./konfig.js");

console.log(konfig.port, typeof konfig.port);
console.log(konfig.maxKoerperKb, typeof konfig.maxKoerperKb);
console.log("debug:", konfig.debug);
console.log("Geheimnis kommt aus der Umgebung:", konfig.sitzungsGeheimnis);

Vier Aufgaben, eine Datei. Sie liest die Umgebung, sie wandelt um (Text zu Zahl, Text zu Wahrheitswert), sie prüft, und sie exportiert ein fertiges Objekt. Danach kommt im ganzen Projekt kein process.env mehr vor, und der Rest der Anwendung arbeitet mit konfig.port als Zahl statt mit einer Zeichenkette, von der man hofft, dass sie eine ist.

Der Wahrheitswert ist dabei die Falle aus 4.2, und sie ist es wert, sie zweimal zu sehen: DEBUG=false ist die Zeichenkette "false", und die ist wahr. Deshalb wird verglichen und nicht geprüft.

Die Prüfung beim Start ist der zweite Gewinn. Fehlt eine Pflichtangabe, bricht das Programm sofort ab und nennt den Namen. Die Alternative ist ein Server, der startet, gut aussieht und drei Stunden später mit einer Fehlermeldung stirbt, die mit der Ursache nichts zu tun hat.

Standardwerte ja, für Secrets nein

Ein Standardwert, der schlimmer ist als keiner
import { createHmac } from "node:crypto";

// Der bequeme Weg: ein Standard, damit es „auch ohne .env laeuft“.
const GEHEIMNIS = process.env.SITZUNGS_GEHEIMNIS ?? "bitte-aendern";

function signiere(wert) {
  return `${wert}.${createHmac("sha256", GEHEIMNIS).update(wert).digest("hex").slice(0, 16)}`;
}

console.log("Server vergibt:", signiere("nutzer=7"));

// Und jetzt jemand anders, der nur den Standardwert kennt und sich
// damit einen Ausweis fuer Nutzer 1 baut:
const gefaelscht = `nutzer=1.${createHmac("sha256", "bitte-aendern").update("nutzer=1").digest("hex").slice(0, 16)}`;

console.log("Fremder faelscht:", gefaelscht);
console.log("Server akzeptiert das:", gefaelscht === signiere("nutzer=1"));

// Der Standardwert steht in deinem Repository. Er ist damit kein
// Geheimnis, sondern eine oeffentliche Zeichenkette, die aussieht wie
// eins. Ohne ihn waere der Server nicht gestartet, und das waere die
// richtige Reaktion gewesen.

Ein Standardwert für den Port ist praktisch. Ein Standardwert für einen Signaturschlüssel ist eine Katastrophe mit Anlauf: Er steht im Repository, also kennt ihn jeder, der das Repository sieht, und damit kann jeder Ausweise ausstellen, die dein Server für echt hält.

Die Regel ist deshalb unbequem und kurz: ein Secret hat keinen Standardwert. Wenn es fehlt, startet die Anwendung nicht. Das nervt beim ersten Mal auf einem neuen Rechner und rettet dich genau einmal, und dieses eine Mal reicht.

Genau daran unterscheidest du die beiden Sorten übrigens verlässlich: Für eine Einstellung kannst du einen sinnvollen Wert hinschreiben. Für ein Secret kannst du das nicht, und wenn es sich doch so anfühlt, ist es keins.

Konfiguration und Secrets sind zwei Dinge

Sie nehmen denselben Weg, nämlich die Umgebung, und werden trotzdem gern verwechselt.

Konfiguration ist alles, was sich zwischen Entwicklung und Betrieb unterscheidet und niemandem schadet, wenn er es liest: Port, Datenbankpfad, Log-Stufe, Basis-URL. Sie darf in der .env.example mit Werten stehen.

Secrets sind alles, womit sich jemand als du ausgeben kann. Sie stehen in der .env.example nur als Name ohne Wert, sie stehen nie im Code, und sie haben keinen Standard. Das war Lektion 15.4, und hier bekommt sie ihre Form: nicht drei einzelne process.env-Zeilen, sondern eine Liste, die geprüft wird.

Zum Mitnehmen

Ein Tippfehler in einem Variablennamen wird nicht zu einem Fehler, sondern zu undefined. Deshalb liest man die Umgebung genau einmal, an einer Stelle, und prüft sie dort.

Jetzt du

Basis Konto, kostenlos

Zu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.

Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.

In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.

Was in dieser Lektion steckt

  • Artikel mit 3 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe, auf dem Server geprüft

    Öffnet sich mit dem Basis Konto.