Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Configuration Management - zarządzanie zasobami

Adres bazy danych wpisany w kod działa doskonale - na Twoim laptopie. Na serwerze testowym baza stoi gdzie indziej, na produkcyjnym jeszcze gdzie indziej, a hasło do niej nie powinno w ogóle trafić do repozytorium. Trzy środowiska, jeden kod, trzy różne wartości - i żadnej nie da się wpisać na stałe.

Rzym miał na to osobny zwyczaj. Rozkazy pisano raz, ale zasoby prowincji spisywano w miejscowym rejestrze: ile zboża, którędy droga, kto zarządza. Ten sam legion wchodził do Galii i do Egiptu, czytając za każdym razem tamtejszy rejestr. W aplikacji tym rejestrem są zmienne środowiskowe.

Cztery kroki

Konfiguracja wchodzi do projektu w ustalonej kolejności - warto ją znać jako całość, bo pominięcie kroku daje mylące błędy:

  1. Utworzenie pliku
    .env
    z kluczami i wartościami.
  2. Rejestracja
    ConfigModule.forRoot()
    w module głównym.
  3. Wstrzyknięcie
    ConfigService
    przez konstruktor.
  4. Odczyt wartości przez
    configService.get('KLUCZ')
    .

Krok pierwszy: plik .env

Zmienne środowiskowe trzymamy w pliku

.env
w katalogu projektu:

1DB_HOST=localhost
2DB_PORT=5432
3DB_PASSWORD=tribute123
4JWT_SECRET=super-secret-key

Format jest minimalny: nazwa, znak równości, wartość. Bez cudzysłowów, bez spacji wokół równa się, bez średników na końcu.

Nazwa pliku nie jest umowna - to

.env
, a nie
config.json
,
settings.yaml
czy
environment.xml
. Te formaty spotkasz w innych ekosystemach, ale nie tutaj.

Sam plik nigdy nie trafia do repozytorium - dopisujesz go do

.gitignore
. Zamiast niego commitujesz
.env.example
z tymi samymi kluczami i pustymi lub przykładowymi wartościami, żeby kolega wiedział, co ma uzupełnić.

Krok drugi: rejestracja modułu

Obsługę konfiguracji daje pakiet

@nestjs/config
. Uwaga na nazwę - nie ma pakietów
@nestjs/env
,
@nestjs/settings
ani
@nestjs/environment
.

Rejestrujemy go w module głównym:

1@Module({
2  imports: [
3    ConfigModule.forRoot({
4      envFilePath: '.env',
5      isGlobal: true,
6    }),
7  ],
8})
9export class AppModule {}

Kolejność elementów jest stała:

ConfigModule.forRoot({
, potem opcje -
envFilePath: '.env',
i
isGlobal: true
- a na końcu
})
.

envFilePath
wskazuje plik do wczytania; przy domyślnej nazwie można go pominąć.

isGlobal: true
sprawia, że
ConfigService
jest dostępny we wszystkich modułach bez dodatkowych importów.
Bez tej opcji musiałbyś importować
ConfigModule
w każdym module z osobna - a używa go zwykle połowa aplikacji. Zwróć uwagę, czego ta opcja nie robi: niczego nie szyfruje, nie ogranicza widoczności do modułu głównego i nie odświeża konfiguracji przy restarcie.

Kroki trzeci i czwarty: odczyt

ConfigService
wstrzykujesz jak każdą inną zależność:

1@Injectable()
2export class DatabaseService {
3  constructor(private configService: ConfigService) {}
4
5  getConnection() {
6    const host = this.configService.get('DB_HOST');
7    const port = this.configService.get<number>('DB_PORT', 5432);
8
9    return { host, port };
10  }
11}

Odczyt składa się z trzech członów w stałej kolejności:

this.configService
,
.get
,
('DB_HOST')
.

Dwa szczegóły warto znać. Zapis

<number>
to parametr typu - mówi TypeScriptowi, czego się spodziewasz; wartości z pliku
.env
przychodzą bowiem zawsze jako tekst. Drugi argument, tutaj
5432
, to wartość domyślna użyta, gdy klucza brak. To wygodne, ale ostrożnie: domyślne hasło albo domyślny klucz JWT to gotowa luka - dla takich wartości lepiej, żeby aplikacja nie wstała, niż żeby wstała z byle czym, @name.

Sprawdzenie przy starcie

Skoro brak klucza daje

undefined
, awaria ujawni się dopiero przy pierwszym użyciu - czasem po godzinach. Lepiej sprawdzić komplet od razu:

1ConfigModule.forRoot({
2  isGlobal: true,
3  validationSchema: Joi.object({
4    DB_HOST: Joi.string().required(),
5    DB_PORT: Joi.number().default(5432),
6    JWT_SECRET: Joi.string().required(),
7  }),
8});

validationSchema
opisuje, jakich kluczy oczekujesz i jakiego typu. Przy starcie
ConfigModule
porówna z nim zawartość
.env
i przerwie uruchomienie, jeśli czegoś brakuje - z komunikatem mówiącym wprost, którego klucza.

To zamiana awarii o trzeciej w nocy na błąd przy wdrożeniu.

Joi
to biblioteka opisu schematów; nie jest częścią NestJS, ale to jej używa
@nestjs/config
.

Podsumowanie

Rejestr prowincji czytany na miejscu, kod jeden dla wszystkich:

  • konfiguracja mieszka poza kodem, bo ta sama aplikacja działa w kilku środowiskach,
  • cztery kroki po kolei: plik
    .env
    ConfigModule.forRoot()
    w module głównym → wstrzyknięcie
    ConfigService
    configService.get('KLUCZ')
    ,
  • zmienne trzymamy w pliku
    .env
    - nie
    config.json
    , nie
    settings.yaml
    , nie
    environment.xml
    ,
  • format: nazwa, znak równości, wartość; plik nie trafia do repozytorium, commitujesz
    .env.example
    ,
  • pakietem jest
    @nestjs/config
    ;
    @nestjs/env
    ,
    @nestjs/settings
    i
    @nestjs/environment
    nie istnieją,
  • rejestracja w kolejności:
    ConfigModule.forRoot({
    ,
    envFilePath: '.env',
    ,
    isGlobal: true
    ,
    })
    ,
  • isGlobal: true
    udostępnia
    ConfigService
    we wszystkich modułach bez dodatkowych importów
    - nie szyfruje i niczego nie ogranicza,
  • odczyt:
    this.configService
    +
    .get
    +
    ('DB_HOST')
    ; wartości z
    .env
    przychodzą zawsze jako tekst,
  • drugi argument
    get
    to wartość domyślna - nie dawaj jej hasłom ani sekretom,
  • validationSchema
    z
    Joi
    sprawdza komplet kluczy przy starcie i przerywa uruchomienie, gdy czegoś brak.

To ostatnia lekcja tego modułu. Umiesz już zbudować moduł, kontroler i serwis, wystawić REST API, opisać dane przez DTO i wyprowadzić konfigurację poza kod - czyli wszystko, czego trzeba, by aplikacja NestJS ruszyła w świat. A na razie zapamiętaj: kod jest jeden dla wszystkich prowincji; różni je tylko rejestr, który czytają na miejscu.

Przejdź do CodeWorlds