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.
Konfiguracja wchodzi do projektu w ustalonej kolejności - warto ją znać jako całość, bo pominięcie kroku daje mylące błędy:
.env z kluczami i wartościami.ConfigModule.forRoot() w module głównym.ConfigService przez konstruktor.configService.get('KLUCZ').Zmienne środowiskowe trzymamy w pliku
w katalogu projektu:.env
1DB_HOST=localhost
2DB_PORT=5432
3DB_PASSWORD=tribute123
4JWT_SECRET=super-secret-keyFormat 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
, a nie .env
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ć.Obsługę konfiguracji daje pakiet
. Uwaga na nazwę - nie ma pakietów @nestjs/config
@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:
, potem opcje - ConfigModule.forRoot({
i envFilePath: '.env',
- a na końcu isGlobal: true
.})
envFilePath wskazuje plik do wczytania; przy domyślnej nazwie można go pominąć.
sprawia, że isGlobal: true
jest dostępny we wszystkich modułach bez dodatkowych importów. Bez tej opcji musiałbyś importować ConfigService
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.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.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.Rejestr prowincji czytany na miejscu, kod jeden dla wszystkich:
.env → ConfigModule.forRoot() w module głównym → wstrzyknięcie ConfigService → configService.get('KLUCZ'),.env - nie config.json, nie settings.yaml, nie environment.xml,.env.example,@nestjs/config; @nestjs/env, @nestjs/settings i @nestjs/environment nie istnieją,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,this.configService + .get + ('DB_HOST'); wartości z .env przychodzą zawsze jako tekst,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.