Kurs NestJS · Moduł 10: Walidacja danych
ValidationPipe - automatyczna stacja kontrolna
W tej lekcji5
Dekoratory z poprzednich lekcji - @IsString, @IsNumber, @Min - niczego same nie sprawdzają. To opis wymagań przypięty do pól, tak jak wykaz przewozowy przypięty do skrzyni. Ktoś musi ten wykaz wziąć do ręki i porównać z zawartością.
Główną rolą ValidationPipe jest automatyczne uruchamianie walidacji dekoratorów class-validator. Nie szyfruje danych w żądaniach HTTP, nie kompresuje odpowiedzi serwera i nie generuje dokumentacji Swagger. Robi jedno: bierze przychodzący obiekt, odczytuje dekoratory z klasy DTO i sprawdza, czy dane im odpowiadają.
Rejestracja globalna
Pipe rejestruje się raz, przy starcie aplikacji:
1async function bootstrap() {
2 const app = await NestFactory.create(AppModule);
3
4 app.useGlobalPipes(
5 new ValidationPipe({
6 whitelist: true,
7 forbidNonWhitelisted: true,
8 transform: true,
9 }),
10 );
11
12 await app.listen(3000);
13}Zapis czyta się w czterech częściach: app to obiekt aplikacji, .useGlobalPipes( otwiera rejestrację, new ValidationPipe({ ... }) tworzy pipe z opcjami, a ) domyka wywołanie.
Zwróć uwagę na liczbę mnogą w nazwie metody: useGlobalPipes przyjmuje wiele pipe'ów po przecinku. W praktyce prawie zawsze podajesz jeden - ValidationPipe - bo pozostałe, jak ParseIntPipe, mają sens tylko przy konkretnych parametrach.
Cztery tryby, od najłagodniejszego
Konfiguracja pipe'a to nie zbiór niezależnych przełączników, tylko drabina restrykcyjności. Warto ją znać w całości, bo wybór trybu jest decyzją o tym, jak twoje API traktuje nieznane dane.
- Bez
ValidationPipe- brak walidacji. Do handlera trafia dowolny obiekt JSON. Adnotacje typów w kodzie są wtedy życzeniem, nie gwarancją. ValidationPipe()- podstawowa walidacja. Reguły z dekoratorów działają, ale pola spoza DTO przechodzą nietknięte.whitelist: true- usuwanie nieznanych pól.whitelist+forbidNonWhitelisted- odrzucanie żądań.
Dwa ostatnie tryby dzieli jedna rzecz i warto ją zapamiętać dokładnie. Opcja whitelist: true automatycznie usuwa z przychodzących danych pola bez dekoratorów walidacji. Nie dodaje białego tła do odpowiedzi, nie tworzy listy dozwolonych adresów IP i nie wyłącza walidacji na wybranych endpointach - po prostu wycina to, czego nie opisałeś w DTO.
Różnica wobec forbidNonWhitelisted: true jest taka, że whitelist usuwa nieznane pola po cichu, a forbidNonWhitelisted zwraca błąd 400. Nie są to opcje identyczne, forbidNonWhitelisted niczego nie wyłącza i nie dopisuje pól do logów serwera.
1// DTO opisuje tylko: name, cohortId
2// Klient przysyła: { name: 'Marcus', cohortId: 3, isAdmin: true }
3
4// ValidationPipe() -> serwis dostaje isAdmin: true
5// whitelist: true -> serwis dostaje { name, cohortId }
6// whitelist + forbidNonWhitelisted -> 400 Bad RequestPole isAdmin w tym przykładzie nie jest przypadkowe. Bez whitelist trafia do serwisu, a jeśli gdzieś dalej ktoś zrobi Object.assign(user, dto), klient właśnie nadał sobie uprawnienia. To najstarszy sposób na przejęcie konta w aplikacjach, które ufają wejściu.
Który tryb wybrać? forbidNonWhitelisted jest surowszy, a przez to uczciwszy: klient dowiaduje się, że przysłał coś nieoczekiwanego, zamiast myśleć, że zapisałeś jego pole. Samo whitelist bywa wygodniejsze przy publicznym API, w którym starsze wersje klientów przysyłają pola już wycofane.
transform
Ostatnia opcja z zestawu produkcyjnego dotyczy typów. transform: true sprawia, że do handlera trafia instancja klasy DTO, a nie zwykły obiekt - i przy okazji konwertuje typy proste. Bez niej @Param('id') id: number daje napis '7', choć typ mówi number, bo z adresu URL wszystko przychodzi jako tekst.
To także warunek działania dekoratorów @Type i @Transform z class-transformer: bez transform: true pozostają one adnotacjami bez skutku.
Na poziomie pojedynczego endpointu
Gdy jeden endpoint potrzebuje innych reguł niż reszta, pipe podpina się lokalnie przez @UsePipes(new ValidationPipe({ ... })) nad metodą. Pipe lokalny ma pierwszeństwo przed globalnym.
Sięgaj po to rzadko. Konfiguracja walidacji rozproszona po kontrolerach szybko przestaje być czytelna, a odpowiedź na pytanie „czy ten endpoint odrzuca nadmiarowe pola?" zaczyna wymagać czytania trzech plików.
Podsumowanie
Wykaz przewozowy sam niczego nie sprawdzi:
- główną rolą
ValidationPipejest automatyczne uruchamianie walidacji dekoratorówclass-validator- nie szyfrowanie, nie kompresja, nie generowanie Swaggera, - rejestracja globalna:
app→.useGlobalPipes(→new ValidationPipe({ ... })→), - tryby od najmniej restrykcyjnego: brak
ValidationPipe→ValidationPipe()→whitelist: true→whitelist+forbidNonWhitelisted, whitelist: trueusuwa z przychodzących danych pola bez dekoratorów walidacji - nie dotyczy adresów IP ani wyłączania walidacji,whitelistusuwa nieznane pola po cichu,forbidNonWhitelistedzwraca błąd400,- nieusunięte pole w rodzaju
isAdminto najprostsza droga do nadania sobie uprawnień, transform: truedaje instancję DTO i konwertuje typy proste; bez niej@Typei@Transformnie działają,@UsePipesnad metodą nadpisuje konfigurację globalną - używaj oszczędnie.
W następnej lekcji zajmiemy się class-transformer - tym, co dzieje się z danymi w drugą stronę, przy wychodzeniu z API. A na razie zapamiętaj: dekoratory opisują, pipe wykonuje. Bez niego masz w kodzie bardzo dokładną dokumentację reguł, których nikt nie egzekwuje.
Kod do tej lekcji: src/validation-pipe.ts
1// ValidationPipe - Konfiguracja stacji kontrolnej
2import { NestFactory } from '@nestjs/core';
3import { ValidationPipe } from '@nestjs/common';
4
5// TODO: Skonfiguruj ValidationPipe globalnie
6async function bootstrap() {
7 const app = await NestFactory.create(AppModule);
8
9 // TODO: Dodaj app.useGlobalPipes z ValidationPipe
10 // Wymagane opcje:
11 // - whitelist: true
12 // - forbidNonWhitelisted: true
13 // - transform: true
14
15 await app.listen(3000);
16 console.log('Brama Imperium otwarta na porcie 3000');
17}
18
19// Przykład użycia per-route
20import { Controller, Post, Body, UsePipes } from '@nestjs/common';
21import { IsString, IsNumber, IsNotEmpty, Min } from 'class-validator';
22
23class CreateSoldierDto {
24 @IsString()
25 @IsNotEmpty()
26 name: string;
27
28 @IsNumber()
29 @Min(16)
30 age: number;
31}
32
33@Controller('soldiers')
34class SoldierController {
35 // TODO: Dodaj @UsePipes(new ValidationPipe({ whitelist: true }))
36 @Post()
37 create(@Body() dto: CreateSoldierDto) {
38 return { message: 'Żołnierz zrekrutowany!', data: dto };
39 }
40}
41
42console.log('ValidationPipe skonfigurowany!');
43Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Jaka jest główna rola ValidationPipe w NestJS?
2. Co robi opcja whitelist: true w ValidationPipe?
To 2 z 3 pytań do tej lekcji. Pozostałe rozwiążesz w grze.
Zadania praktyczne w grze
- Edytor kodu
Uzupełnij konfigurację ValidationPipe w funkcji bootstrap
- Układanie w poziomie
Ułóż elementy konfiguracji globalnego ValidationPipe w prawidłowej kolejności:
- Edytor kodu
Uzupełnij komentarze opisujące zachowanie każdego trybu ValidationPipe
- Układanie w pionie
Uporządkuj tryby ValidationPipe od najmniej do najbardziej restrykcyjnego: