Zarządzanie lokalnymi zmiennymi środowiskowymi - direnv

Problem

W trakcie dewelopmentu ładowanie i czytanie lokalnych zmiennych środowiskowych może przysporzyć wielu problemów. Jeśli nie używam docker'a i nie ładuje zmiennych za jego pomocą, dodawanie ich w standardowy linux'owy sposób 'export NAME=VALUE' w terminalu jest bardzo uciążliwe.

Zazwyczaj z pomocą przychodzą pakiety pozwalające automatycznie ładować zmienne z pliku, np. .env. Dla Node będzie to dobrze znany dotenv. Użycie jest bardzo proste, tworzymy plik .env i dodajemy do niego wszystkie zmienne środowiskowe, których chcemy użyć w trakcie dewelopmentu.

.env
PORT=5000

Na przykładzie aplikacji z poprzedniego artykułu, pakietu dotenv można by użyć w następujący sposób:

src/index.ts
import express, { Request, Response } from "express";
import * as dotenv from 'dotenv' // import paczki
dotenv.config() // załadowanie zmiennych do process.env

const app = express();
const PORT = process.env.PORT;

app.get("/", (req: Request, res: Response) => {
  res.send("Hello World!");
});

app.listen(PORT, () => {
  console.log('Server started on port ${PORT}!');
});

Teraz wystarczy zainstalować pakiet jako dev dependency.

terminal
npm i -D dotenv

Dzięki paczce mam dostęp do wszystkich zmiennych środowiskowych. Co więcej, jeśli 'dotenv.config()' zostanie wywołany w głównym pliku aplikacji (app entry point), również we wszystkich importowanych modułach mam dostęp do zmiennych poprzez obiekt 'process.env'.

Sposób rozwiązania problemu sam w sobie nie jest zły. Wszystko działa poprawnie, lecz problemem zaczyna być dodatkowa dependencja, która służy tylko do dewelopmentu. Również te dwie dodatkowe linijki kodu nie robią wielkiej różnicy, ale po co je dodawać, gdy nie są wymagane.

direnv

direnv nie jest dependencją Node, a rozszerzeniem powłoki terminala. Pozwala na automatyczne ładowanie zmiennych środowiskowych w zależności od katalogu, w którym się znajduje. Wystarczy, aby w katalogu znajdował się plik '.envrc' precyzujący jak mają być załadowane zmienne i na jego podstawie ładuje zmienne do sub-powłoki terminala.

Instalacja

Większość dystrybucji linuxa oraz MacOS ma już zainstalowany ten program.

Aby działał on poprawnie z powłoką terminala, trzeba przypiąć jego wywołanie w pliku konfiguracyjnym powłoki:

  • dla BASH, zmiana w ~/.bashrc: 'eval "$(direnv hook bash)"'
  • dla ZSH, zmiana w ~/.zshrc: 'eval "$(direnv hook zsh)"'

Po więcej spieranych powłok odsyłam do dokumentacji.

Proste użycie

Teraz, aby móc używać zmiennych środowiskowych tak jak dotąd, to znaczy ładować je z pliku .env, wystarczy dodać kolejny plik .envrc. Teraz wystarczy odpalić komendę 'direnv allow .', która pozwoli paczce direnv na automatyczne ładowanie zmiennych środowiskowych.

Aby dalej móc ładować zmienne z pliku .env możemy przekazać pakietowi, aby załadował zmienne właśnie z tego pliku dzięki poleceniu 'direnv dotenv [shell] [path_to_.env_file]', w naszym wypadku będzie to:

terminal
direnv dotenv zsh ./.env

Teraz gdy w tym samym katalogu wystartujemy aplikacje, będzie ona miała załadowane wszystkie zmienne sprecyzowane w pliku.

Aby uprościć cały flow, możemy w pliku .envrc podać bezpośrednio komendę 'dotenv'.

.envrc
dotenv

W takim wypadku, o ile .env i .envrc znajdują się w tym samym katalogu, pakiet direnv będzie zawsze automatycznie ładował zmienne w tym katalogu. Można wtedy oczywiście wyrzucić dependencje na dotenv oraz wszystkie fragmenty kodu z nim związane. Rozwiązanie działa również niezależnie od języka programowanie aplikacji!