Obóz ćwiczebny wybacza wszystko. Wał jest niski, bo i tak nikt nie szturmuje, prowiant przywożą codziennie, a jak coś się zawali, stawia się to od nowa po obiedzie. Fort na granicy nie wybacza niczego - i to nie dlatego, że zbudowano go inaczej, tylko dlatego, że nikt tam nie stoi obok, żeby poprawić.
Deployment to ta chwila przeprowadzki. Aplikacja, którą uruchamiałeś poleceniem
yarn start:dev, ma zacząć działać sama, przez wiele dni, dla ludzi, których nie znasz. Ta lekcja jest o czterech rzeczach, które trzeba zrobić, zanim to się stanie.W trybie deweloperskim NestJS tłumaczy TypeScript na bieżąco, przy każdym zapisie pliku. Na produkcji tłumaczymy raz, z góry:
1{
2 "scripts": {
3 "build": "nest build",
4 "start:dev": "nest start --watch",
5 "start:prod": "node dist/main"
6 }
7}Polecenie
kompiluje TypeScript do JavaScriptu w katalogu nest build
- i tylko tyle. Nie generuje dokumentacji Swagger (tę dodaje się osobno, dekoratorami i dist/
SwaggerModule), nie instaluje zależności z package.json (od tego jest yarn install), nie uruchamia trybu watch (to start:dev z flagą --watch).Po kompilacji uruchamiasz
- zwykły Node.js na zwykłym pliku node dist/main
.js. Na serwerze produkcyjnym TypeScript nie jest już potrzebny, bo nie ma czego tłumaczyć.NODE_ENV to zmienna środowiskowa, którą czytają niemal wszystkie biblioteki ekosystemu Node.js. Ustawienie NODE_ENV=production włącza optymalizacje produkcyjne i wyłącza szczegółowe logowanie.To jedyne, co robi. Nie uruchamia testów automatycznych - te uruchamia
yarn test. Nie włącza trybu hot-reload; przeciwnie, hot-reload to sprawa środowiska deweloperskiego. Nie generuje dokumentacji API. Zmiana jednego napisu nie dodaje aplikacji funkcji - ona tylko przestawia to, co już w niej jest, z trybu „pomagaj programiście" na tryb „obsługuj ruch".Skutki bywają zaskakująco duże: biblioteki pomijają kosztowne sprawdzenia, cache szablonów zostaje włączony, a stos wywołań przestaje trafiać do odpowiedzi błędu - bo użytkownikowi jest niepotrzebny, a atakującemu bardzo przydatny.
NODE_ENV jest tylko jedną z nich. Adres bazy danych, sekret do podpisywania tokenów, klucz do bramki płatności - to wszystko zmienne środowiskowe: wartości, które podaje się aplikacji z zewnątrz, zamiast wpisywać do kodu.1NODE_ENV=production
2PORT=3000
3DATABASE_URL=postgres://legion:haslo@db.limes.internal:5432/tributes
4JWT_SECRET=zmien-mnie-przed-wymarszemPowód jest praktyczny: ten sam zbudowany
dist/ musi działać na twoim komputerze, na środowisku testowym i na produkcji. Różni je wyłącznie zestaw zmiennych. Gdyby adres bazy siedział w kodzie, każde środowisko wymagałoby osobnej kompilacji - a wtedy to, co przetestowałeś, nie byłoby tym, co uruchamiasz.Dlatego konfiguracja zmiennych to pierwszy krok przygotowania, przed jakąkolwiek kompilacją.
Plik
main.ts startuje aplikację. W wersji deweloperskiej ma zwykle dwie linijki; w produkcyjnej dokłada się do niego cztery rzeczy, w tej kolejności:1async function bootstrap() {
2 const app = await NestFactory.create(AppModule);
3
4 app.use(helmet());
5 app.use(compression());
6 app.enableCors({ origin: 'https://legion.imperium.rome' });
7
8 app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
9
10 const port = process.env.PORT || 3000;
11 await app.listen(port);
12}Kolejność nie jest przypadkowa:
musi być pierwsze, bo dopiero wtedy istnieje obiekt aplikacji; const app = await NestFactory.create(AppModule)
i pozostałe middleware ustawia się przed obsługą ruchu; app.use(helmet())
rejestruje walidację; app.useGlobalPipes(new ValidationPipe())
otwiera port i od tej chwili aplikacja przyjmuje żądania. Cokolwiek dopiszesz po await app.listen(port)
listen, wykona się już przy działającym serwerze.Każdy z tych czterech elementów odpowiada za co innego:
helmet ustawia nagłówki HTTP bezpieczeństwa - Content-Security-Policy (CSP), X-Frame-Options, Strict-Transport-Security i kilkanaście innych. Nie szyfruje bazy danych, nie kompresuje plików statycznych i nie zarządza sesjami użytkowników; to hełm dla odpowiedzi, nie dla danych.compression pakuje odpowiedzi algorytmem gzip, zanim wyjdą na sieć. Kilkusetkilobajtowy JSON potrafi zejść do kilkudziesięciu kilobajtów.enableCors rozstrzyga, ze których stron przeglądarka może wołać twoje API. Na produkcji podaje się konkretny adres, nie gwiazdkę.ValidationPipe to znany ci już strażnik danych wejściowych, tu włączony globalnie.Cała droga na produkcję układa się w kolejność, której nie da się poprzestawiać:
helmet, compression i CORS - bo to zmiany w kodzie, a kod zaraz zamrozimy.nest build - powstaje katalog dist/, od tej chwili niezmienny.node dist/main w produkcji - startuje to, co zbudowałeś, z tymi zmiennymi, które zastanie.Zamiana kroków drugiego i trzeciego to najczęstszy błąd tej listy: zbudowany
dist/ nie zna zmian, które dopisałeś po kompilacji. Aplikacja wstanie, nie zgłosi żadnego błędu i będzie działać bez nagłówków bezpieczeństwa.Fort rusza na granicę, @name:
nest build kompiluje TypeScript do JavaScriptu w katalogu dist/ - nie generuje Swaggera, nie instaluje zależności, nie uruchamia trybu watch,node dist/main; TypeScript nie jest tam już potrzebny,NODE_ENV=production włącza optymalizacje produkcyjne i wyłącza szczegółowe logowanie - nie uruchamia testów, nie włącza hot-reloadu, nie generuje dokumentacji,main.ts: const app = await NestFactory.create(AppModule) → app.use(helmet()) → app.useGlobalPipes(new ValidationPipe()) → await app.listen(port),helmet ustawia nagłówki HTTP bezpieczeństwa (CSP, X-Frame-Options i inne) - nie szyfruje bazy, nie kompresuje plików statycznych, nie zarządza sesjami,compression pakuje odpowiedzi gzipem, enableCors wskazuje dozwolone źródła żądań,helmet, compression, CORS → nest build → node dist/main.W następnej lekcji zamkniemy fort w kontenerze, żeby jego wnętrze wyglądało tak samo na każdej maszynie. A na razie zapamiętaj: produkcja nie jest trudniejszą wersją środowiska deweloperskiego. Jest tą samą aplikacją bez nikogo, kto poprawi.