Kurs NestJS · Moduł 9: Deployment i infrastruktura
Deployment - wyprowadzenie fortu na pole bitwy
W tej lekcji6
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.
nest build - kompilacja
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 nest build kompiluje TypeScript do JavaScriptu w katalogu dist/ - i tylko tyle. Nie generuje dokumentacji Swagger (tę dodaje się osobno, dekoratorami i 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 node dist/main - zwykły Node.js na zwykłym pliku .js. Na serwerze produkcyjnym TypeScript nie jest już potrzebny, bo nie ma czego tłumaczyć.
NODE_ENV - jeden napis, wiele skutków
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.
Zmienne środowiskowe - to, co różni fort od obozu
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ą.
Produkcyjny main.ts
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: const app = await NestFactory.create(AppModule) musi być pierwsze, bo dopiero wtedy istnieje obiekt aplikacji; app.use(helmet()) i pozostałe middleware ustawia się przed obsługą ruchu; app.useGlobalPipes(new ValidationPipe()) rejestruje walidację; await app.listen(port) otwiera port i od tej chwili aplikacja przyjmuje żądania. Cokolwiek dopiszesz po listen, wykona się już przy działającym serwerze.
Każdy z tych czterech elementów odpowiada za co innego:
helmetustawia nagłówki HTTP bezpieczeństwa -Content-Security-Policy(CSP),X-Frame-Options,Strict-Transport-Securityi 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.compressionpakuje odpowiedzi algorytmem gzip, zanim wyjdą na sieć. Kilkusetkilobajtowy JSON potrafi zejść do kilkudziesięciu kilobajtów.enableCorsrozstrzyga, ze których stron przeglądarka może wołać twoje API. Na produkcji podaje się konkretny adres, nie gwiazdkę.ValidationPipeto znany ci już strażnik danych wejściowych, tu włączony globalnie.
Cztery kroki
Cała droga na produkcję układa się w kolejność, której nie da się poprzestawiać:
- Skonfiguruj zmienne środowiskowe - bo od nich zależy, do jakiej bazy aplikacja się połączy.
- Dodaj
helmet,compressioni CORS - bo to zmiany w kodzie, a kod zaraz zamrozimy. - Uruchom
nest build- powstaje katalogdist/, od tej chwili niezmienny. - Uruchom
node dist/mainw 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.
Podsumowanie
Fort rusza na granicę:
nest buildkompiluje TypeScript do JavaScriptu w katalogudist/- nie generuje Swaggera, nie instaluje zależności, nie uruchamia trybu watch,- na produkcji uruchamiasz
node dist/main; TypeScript nie jest tam już potrzebny, NODE_ENV=productionwłącza optymalizacje produkcyjne i wyłącza szczegółowe logowanie - nie uruchamia testów, nie włącza hot-reloadu, nie generuje dokumentacji,- zmienne środowiskowe pozwalają uruchomić ten sam zbudowany katalog na każdym środowisku - dlatego konfiguruje się je jako pierwsze,
- kolejność w
main.ts:const app = await NestFactory.create(AppModule)→app.use(helmet())→app.useGlobalPipes(new ValidationPipe())→await app.listen(port), helmetustawia nagłówki HTTP bezpieczeństwa (CSP, X-Frame-Options i inne) - nie szyfruje bazy, nie kompresuje plików statycznych, nie zarządza sesjami,compressionpakuje odpowiedzi gzipem,enableCorswskazuje dozwolone źródła żądań,- cztery kroki przygotowania: zmienne środowiskowe →
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.
Kod do tej lekcji: src/deployment-intro.ts
1// Deployment - Wyprowadzenie Fortu na Pelne Imperium
2import { NestFactory } from '@nestjs/core';
3import { ValidationPipe, Logger } from '@nestjs/common';
4
5// 1. Konfiguracja produkcyjna main.ts
6async function bootstrap() {
7 const logger = new Logger('Bootstrap');
8
9 // Tworzymy aplikacje z ograniczonym logowaniem
10 const app = await NestFactory.create(AppModule, {
11 logger: ['error', 'warn', 'log'],
12 });
13
14 // Bezpieczenstwo - Helmet
15 // app.use(helmet());
16
17 // Kompresja odpowiedzi - zmniejsza rozmiar o 60-80%
18 // app.use(compression());
19
20 // CORS - kontrola dostepu
21 app.enableCors({
22 origin: process.env.CORS_ORIGIN || 'http://localhost:3000',
23 credentials: true,
24 });
25
26 // Globalna walidacja
27 app.useGlobalPipes(new ValidationPipe({
28 whitelist: true, // Usun nieznane pola
29 forbidNonWhitelisted: true, // Blad przy nieznanych polach
30 transform: true, // Automatyczna transformacja typow
31 }));
32
33 // Prefix API
34 app.setGlobalPrefix('api/v1');
35
36 // Graceful Shutdown - zamknij polaczenia przed wylaczeniem
37 app.enableShutdownHooks();
38
39 const port = process.env.PORT || 3000;
40 await app.listen(port, '0.0.0.0');
41
42 logger.log(`Application running on port ${port}`);
43 logger.log(`Environment: ${process.env.NODE_ENV}`);
44}
45
46// 2. Roznice miedzy srodowiskami
47// Development: hot-reload, debug, verbose logging
48// Staging: produkcja-like, test data
49// Production: zoptymalizowane, ograniczone logi, bezpieczenstwo
50
51// 3. Skrypty w package.json
52const scripts = {
53 'start:dev': 'nest start --watch',
54 'start:debug': 'nest start --debug --watch',
55 'build': 'nest build',
56 'start:prod': 'node dist/main',
57 'test': 'jest',
58 'test:cov': 'jest --coverage',
59 'test:e2e': 'jest --config ./test/jest-e2e.json',
60 'lint': 'eslint "{src,test}/**/*.ts" --fix',
61};
62
63// 4. Checklist przed deploymentem
64const checklist = [
65 'Testy przeszly (unit + e2e)',
66 'Zmienne srodowiskowe ustawione',
67 'Baza danych zmigrowana',
68 'Sekrety bezpieczne (nie w kodzie!)',
69 'CORS skonfigurowany',
70 'Health check endpoint gotowy',
71 'Monitoring wlaczony',
72 'Backup strategy gotowa',
73];
74Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Komenda nest build w NestJS:
2. Ustawienie NODE_ENV=production w aplikacji NestJS:
To 2 z 3 pytań do tej lekcji. Pozostałe rozwiążesz w grze.
Zadania praktyczne w grze
- Edytor kodu
Skonfiguruj main.ts z helmet, compression, CORS i ValidationPipe dla produkcji
- Układanie w pionie
Uporządkuj kroki przygotowania aplikacji NestJS do produkcji:
- Klikanie w kolejności
Ułóż elementy konfiguracji main.ts w poprawnej kolejności: