Node.js und Logging – Teil 2: Der EnvVarService

Unsere Node.js-Applikation soll je Umgebung anders konfiguriert werden. Hierfür nutzen wir Umgebungsvariablen. Damit wir innerhalb der Applikation typsicheren Zugriff haben, schreiben wir einen Service, der beim Applikationsstart sicherstellt, dass alles korrekt konfiguriert ist, und wenn nicht, sofort einen Hinweis zur Fehlerursache gibt.

5 Minuten

Nachdem wir im Teil 1 den LogService vorbereitet haben, ist nun das Ziel, einen Service für den sicheren Zugriff auf Umgebungsvariablen bereitzustellen. Mit Sicherheit meine ich zweierlei Dinge. Zum einen, dass die Umgebungsvariablen gesetzt sind. Zum anderen, dass die Zeichenkette inhaltlich den Anforderungen unserer Applikation entspricht – z. B. die Transformation in Listen, Flags oder Zahlen. Dafür nutzen wir das NPM-Paket Zod.

Zur Zielerreichung implementieren wir einen EnvVarService, der uns Zugriff auf zwei Umgebungsvariablen liefert:

  • NODE_ENV gibt uns Auskunft darüber, in welchem Modus die Node.js-Applikation läuft.
  • SERVER_PORT definiert den Port, auf dem die Applikation lauschen soll.

Definition erlaubter Werte für NODE_ENV

Die Umgebungsvariable NODE_ENV kann grundsätzlich jeglichen Wert annehmen, den wir ihr zuweisen. Damit wir nicht Gefahr laufen, zwischen prod und production oder dev und development zu raten, legen wir die Werte fest. Hierfür definieren wir den erlaubten Wertebereich als Enum in der Datei types.ts im Verzeichnis src.

export enum NodeEnvironments {  
  DEVELOPMENT = 'development',  
  PRODUCTION = 'production',
  TEST = 'test',
}

Erstellen des EnvVarServices

Im Ordner services erstellen wir eine neue Datei namens EnvVarService.ts. Dieser Service ist unsere zentrale Zugriffsstelle auf Werte, die wir aus den Umgebungsvariablen entnehmen. Damit wir keine lange Liste haben, gliedern wir die Werte nach Kontext. Zusätzlich stellen wir eine Hilfsfunktion bereit, um für alle Umgebungen außer Production Standardwerte festzulegen.

Warum keine Standardwerte in Production?

Eine Änderung eines Standardwerts ist der Definition nach ein Breaking Change für Production, weil sich das exakte Verhalten nicht vorhersehen lässt. Im besten Fall stürzt die Applikation ab und wir haben sofort volle Aufmerksamkeit für das Problem. Im schlimmsten Fall verhält sich die Applikation anders, und wir erfahren vom Problem hoffentlich durch Monitoring oder Logs – die Zuordnung des Fehlers zu etwas Trivialem wie der Anpassung eines Standardwerts ist noch eine ganz eigene Herausforderung. Glaubt mir das bitte: Ich habe das bereits für euch ausprobiert … mehrmals. Deshalb niemals Standardwerte nutzen, sondern immer alles explizit definieren – am besten per Infrastructure as Code.

Zurück zur Hilfsmethode, die wir wie folgt definieren:

import { z } from 'zod'

// We create a helper function to set default values only in non-production mode 
// eslint-disable-next-line @typescript-eslint/no-explicit-any  
export const withDevDefault = <T extends z.ZodTypeAny>(schema: T, val: any) =>  
  process.env['NODE_ENV'] !== NodeEnvironments.PRODUCTION ? schema.default(val) : schema

Nun gilt es, die Umgebungsvariablen einzulesen, mit dem Zod-Parser zu validieren, wenn nötig, Fehler auszugeben und die geprüften Werte typsicher verfügbar zu machen.


const parseBaseValues = () => {  
  // Base schema for all app-related environment variables  
  const schema = z.object({  
    NODE_ENV: withDevDefault(z.enum(NodeEnvironments), NodeEnvironment.DEVELOPMENT),  
    SERVER_PORT: withDevDefault(z.coerce.number().positive(), 8080),  
  })  

  const parsed = schema.safeParse(process.env)  

  // handle error, so app doesn't start with invalid env vars  
  if (!parsed.success) {  
    throw new Error(  
      `❌ Invalid environment variables in app schema: ${JSON.stringify(parsed.error, null, 2)}`,  
    )  
  }  

  return {  
    nodeEnv: parsed.data.NODE_ENV,  
    serverPort: parsed.data.SERVER_PORT,  
  }  
}

// Export the parsed env vars per context: e.g. app or logging  
export default {  
  app: parseBaseValues(),  
}

Ich möchte an dieser Stelle folgende Aspekte hervorheben:

  • Die Variable SERVER_PORT wird vom String-Typ in den Number-Typ umgewandelt und soll einen positiven Wert haben. Probier es mit export SERVER_PORT=0 && npm run start:dev aus.
  • Die Variable NODE_ENV ist ein String, erlaubt jedoch nur zwei Ausprägungen, die wir im Enum definiert haben. Benötigen wir eine weitere Umgebung, reicht es den Enum zu erweitern – z. B. QA oder dergleichen. Probier es mit export NODE_ENV=dev && npm run start:dev aus.

Die resultierende Fehlerausgabe zeigt die genaue Fehlerquelle und mit der Zod-Fehlerausgabe haben wir eine präzise Vorstellung davon, was falsch konfiguriert ist.

Beispielhafte Nutzung des Services

Mit dem neuen Service können wir den Applikationsstart dahingehend verfeinern, dass wir uns ausgeben lassen, in welchem Kontext wir laufen und welchen Port wir nutzen.

import createApp from '@/createApp'  
import LogService from '@/services/LogService'  
import EnvVarService from '@/services/EnvVarService'  

const PORT = EnvVarService.app.serverPort  
const NODE_ENV = EnvVarService.app.nodeEnv  

const app = createApp()  

app.listen(PORT, () => {  
  LogService.debug(`Server is started`, {port: PORT, nodeEnv: NODE_ENV})  
})

Fazit

Mit der Anpassung können wir unsere Applikation starten, ohne Umgebungsvariablen zu definieren. Dies hat folgende Ausgabe zur Folge:

[2026-01-03T12:01:23.659Z] debug Server is started [{"port":8080,"nodeEnv":"development"}]

Hier wird noch einmal deutlich, was ich oben erwähnt habe. Statt auf Port 4000 zu lauschen, lauschen wir auf Port 8080. Ein Breaking Change, der in der Produktivumgebung zu Problemen geführt hätte. Um auf dem vorherigen Port zu lauschen, können wir die Applikation wie folgt starten:

export SERVER_PORT=4000 && npm run start:dev

[2026-01-03T12:06:16.580Z] debug Server is started [{"port":4000,"nodeEnv":"development"}]

Damit wir zukünftig nicht über einen Exportbefehl die Umgebungsvariablen setzen müssen, werden wir im nächsten Artikel das NPM-Paket dotenv einbinden.

call to action background image

Abonniere meinen Newsletter

Erhalte einmal im Monat Nachrichten aus den Bereichen Softwareentwicklung und Kommunikation gespikt mit Buch- und Linkempfehlungen.