Friedrich Siever

14. Januar 2024

Nest.js - Konfiguration und Envrionments

von: Friedrich Siever | Last Updated: 14.01.24

Einführung

Dieser Beitrag widmet sich der effizienten Verwaltung von Nest.js-Konfigurationen in unterschiedlichen Umgebungen. Praktisch jedes Softwareprojekt erfordert heute spezifische Umgebungen, sei es für die Entwicklung, Produktion oder sogar Test- und Staging-Zwecke. Wir werden detailliert erkunden, wie das Nest-Config-Modul eine elegante Lösung für diese Herausforderungen bietet, indem es die Konfigurationen intelligent in verschiedenen Umgebungen handhabt.

Obwohl die Trennung von Konfigurationen auf den ersten Blick einfach erscheinen mag, gestaltet sich dies in der Praxis oft komplexer. Hier kommt das Nest-Config-Modul zum Glück ins Spiel und bietet eine ausgezeichnete Lösung.

Wir werden in diesem Beitrag erörtern, wo und wie wir unsere Nest.js-Konfiguration in verschiedenen Umgebungen verwalten können.

Warum verschiedene Umgebungen?

In der Realität benötigen wir häufig separate Umgebungen für Entwicklung, Produktion und möglicherweise auch für Tests und Staging. Die Herausforderung besteht darin, wie wir diese unterschiedlichen Konfigurationen effektiv separieren können.

Das Config-Modul

Nest stellt uns hierfür das beeindruckende Config-Modul zur Verfügung, das wir in diesem Beitrag genauer unter die Lupe nehmen werden.

Installation

Um loszulegen, installieren wir das Modul mit dem folgenden Befehl:

npm i @nestjs/config

Im Anschluss kannst du das Config Modul in deiner app.module.ts registrieren:

// src/app.module.ts

// ...

@Module({
  imports: [
    ConfigModule.forRoot(),
    //...
  ],
  // ...
})
export class AppModule {}

Das bewirkt, dass Variablen von einer .env Datei geladen werden. Lass uns also eine solche Datei erstellen. Von dieser Einrichtung profitierst du bereits in frühen Phasen deines Projektes

.env

Grundlagen zu process.env in Node.js

Das @nestjs/config-Modul ermöglicht es uns, Umgebungsvariablen einfach über process.env zu verwenden. Beispielsweise können wir in unserem Code auf die in der .env-Datei definierte DATABASE_HOST-Variable zugreifen:

const databaseHost = process.env.DATABASE_HOST;

Typischerweise speichern wir vertrauliche Informationen wie Credentials, Schlüssel und Tokens in Umgebungsvariablen. Dies erhöht die Sicherheit deiner Anwendung, da sensible Daten nicht im Quellcode fest verankert sind. Hierbei ist die .env-Datei besonders relevant.

Um dieses zu vereinfachen können wir sogenannte .env Files in der Entwicklungsumgebung, aber auch auf Produktionsservern verwenden.

Eine erste .env File

Deine .env File erstellst du am besten im project Root Verzeichnis. Hierin können wir die environment Variablen festlegen:

DB_HOST = localhost
DB_PORT = 5432
DB_USERNAME = DeinUser
DB_PASSWORD = DeinPasswort
DB_NAME = nest-js-exhibitions

Durch die Aktivierung des Config-Moduls von Nest.js kannst du Konfigurationswerte direkt in deinem App-Modul verwenden. Dabei ist es wichtig zu beachten, dass die .env-Dateien nicht in deinen Versionskontroll-Dateien veröffentlicht werden sollten, um die Prinzipien des 12-Faktor-Apps zu respektieren, insbesondere Regel 3 von 12.

Einbindung des Config-Moduls

// ..
@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: '.env',
    }),
    TypeOrmModule.forRoot({
      type: 'postgres',
      host: process.env.DB_HOST,
      port: Number(process.env.DB_PORT),
      username: process.env.DB_USERNAME,
      password: process.env.DB_PASSWORD,
      database: process.env.DB_NAME,
      entities: [Exhibition],
      synchronize: true,
    }),

    ExhibitionsModule, // <-!!!
  ],
  // ...
})

Erstelle im src-Ordner deiner Anwendung einen neuen Ordner namens config. In diesem Ordner erstellst du eine Datei, beispielsweise orm.config.ts, um die Konfigurationslogik für TypeORM zu verwalten.

// src/config/orm.config.ts
import { TypeOrmModuleOptions } from "@nestjs/typeorm";
import { Exhibition } from "src/exhibitions/exhibitions.entity";

export default (): TypeOrmModuleOptions => ({
  type: "postgres",
  host: process.env.DB_HOST,
  port: Number(process.env.DB_PORT),
  username: process.env.DB_USERNAME,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,
  entities: [Exhibition],
  synchronize: true,
});

In der Datei src/app.module.ts werden die Konfigurationen für die gesamte Anwendung initialisiert. Hierbei werden unter anderem die Umgebungsvariablen durch das ConfigModule geladen, und die TypeORM-Konfiguration wird durch die Verwendung von TypeOrmModule.forRootAsync dynamisch gestaltet. Auf diesem Weg können wir auch Funktionen verwenden.

// ..
@Module({
 imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      load: [ormConfig],
    }),
    TypeOrmModule.forRootAsync({
      useFactory: ormConfig,
    }),

    ExhibitionsModule, // <-!!!
  ],
  // ...
})

Beachte. Über die Load Methode des ConfigModuls laden wir den Default Export aus unserer ORM Konfiguration, während wir beim TypeOrmModule nun eine neue Methode brauchen .forRootAsync ermöglicht es uns hierbei funktionen und nicht nur “Plain Objects” zu verwenden.

Auch wenn das Vorgehen auf den ersten Blick etwas komplex anmutet, bietet es praktische Vorteile. Zum Beispiel können wir unsere Konfigurationen mit Namespaces versehen.

import { registerAs } from "@nestjs/config";
import { TypeOrmModuleOptions } from "@nestjs/typeorm";
import { Exhibition } from "src/exhibitions/exhibitions.entity";

// Namespacing via register As
export default registerAs(
  "orm.config",
  (): TypeOrmModuleOptions => ({
    type: "postgres",
    host: process.env.DB_HOST,
    port: Number(process.env.DB_PORT),
    username: process.env.DB_USERNAME,
    password: process.env.DB_PASSWORD,
    database: process.env.DB_NAME,
    entities: [Exhibition],
    synchronize: true,
  }),
);

Dieses Konfigurationssetup führt nun dazu, dass wir unsere orm.config.ts einfach kopieren und für andere Environments nutzbar machen können. Erstelle im Config Ordner hierzu einfach eine Datei mit z.B. dem Namen orm.config.prod.ts. Nach den gewünschten Änderungen könnte diese z.B. so aussehen:

import { registerAs } from "@nestjs/config";
import { TypeOrmModuleOptions } from "@nestjs/typeorm";
import { Exhibition } from "src/exhibitions/exhibitions.entity";

// Namespacing via register As
export default registerAs(
  "orm.config.pord",
  (): TypeOrmModuleOptions => ({
    type: "postgres",
    host: process.env.DB_HOST,
    port: Number(process.env.DB_PORT),
    username: process.env.DB_USERNAME,
    password: process.env.DB_PASSWORD,
    database: process.env.DB_NAME,
    entities: [Exhibition],
    synchronize: false,
  }),
);

Jetzt können wir die Nutzung unserer Konfiguration dynamisch mit dem Environment verbinden.

@Module({
imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      load: [ormConfig],
      expandVariables: true,
    }),
    TypeOrmModule.forRootAsync({
      useFactory:
        process.env.NODE_ENV !== 'production' ? ormConfig : ormConfigProd,
    }),
    ExhibitionsModule, // <-!!!
  ],
  // ...
})

Das obige Beispiel dient lediglich zur Veranschaulichung und repräsentiert noch keine Best Practice für diesen speziellen Fall. Dennoch ist es von entscheidender Bedeutung zu verstehen, wie wir solche Konzepte in Nest.js umsetzen können.

Es könnte dir aufgefallen sein, dass ich den expandVariables-Parameter auf true gesetzt habe. Dies ist eine bemerkenswerte Funktion von Nest.js, die es ermöglicht, .env-Dateien mit einer gewissen Intelligenz zu versehen. Zum Beispiel:

APP_URL = mywebsite.com
SUPPORT_EMAIL = support@${APP_URL}

Fazit

Fazit: “Das vorgestellte Konfigurationssetup in Nest.js ermöglicht eine flexible und saubere Verwaltung von Umgebungsvariablen und Konfigurationen. Durch die Nutzung des Nest-Config-Moduls und die Integration von .env-Dateien wird nicht nur die Sicherheit der Anwendung verbessert, sondern auch die Entwicklungs- und Deploymentschritte vereinfacht. Die intelligente Verwendung von Funktionen und Namespaces bietet eine skalierbare Lösung für verschiedene Umgebungen, während die Einbindung von expandVariables die Konfiguration zusätzlich verfeinert. Obwohl das Beispiel nicht als endgültige Best Practice betrachtet werden sollte, vermittelt es wichtige Prinzipien, wie sie in Nest.js angewendet werden können, um Konfigurationsherausforderungen zu bewältigen.

Zur Konfiguration des Loggings habe ich einen eigenen Beitrag geschrieben.

0
0 Bewertungen

Jetzt selbst bewerten