Kurs NestJS · Moduł 9: Deployment i infrastruktura

Deployment - wyprowadzenie fortu na pole bitwy

5 min czytania
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-wymarszem

Powó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:

  • 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.

Cztery kroki

Cała droga na produkcję układa się w kolejność, której nie da się poprzestawiać:

  1. Skonfiguruj zmienne środowiskowe - bo od nich zależy, do jakiej bazy aplikacja się połączy.
  2. Dodaj helmet, compression i CORS - bo to zmiany w kodzie, a kod zaraz zamrozimy.
  3. Uruchom nest build - powstaje katalog dist/, od tej chwili niezmienny.
  4. Uruchom 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.

Podsumowanie

Fort rusza na granicę:

  • nest build kompiluje TypeScript do JavaScriptu w katalogu dist/ - 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=production włą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),
  • 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ń,
  • 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];
74

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Komenda nest build w NestJS:

  2. 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:

Przydatne artykuły