Cours NestJS · Module 10: Validation des données
ValidationPipe - le poste de contrôle automatique
Dans cette leçon5
Les décorateurs des leçons précédentes - @IsString, @IsNumber, @Min - ne vérifient rien par eux-mêmes. Ils sont une description d'exigences épinglée aux champs, comme un bordereau épinglé à une caisse. Quelqu'un doit prendre ce bordereau en main et le comparer au contenu.
Le rôle principal de ValidationPipe est d'exécuter automatiquement la validation des décorateurs de class-validator. Il ne chiffre pas les données des requêtes HTTP, ne compresse pas les réponses du serveur et ne génère pas la documentation Swagger. Il fait une chose : il prend l'objet entrant, lit les décorateurs de la classe DTO et vérifie si les données y répondent.
L'enregistrement global
Le pipe s'enregistre une fois, au démarrage de l'application :
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}L'écriture se lit en quatre parties : app est l'objet application, .useGlobalPipes( ouvre l'enregistrement, new ValidationPipe({ ... }) crée le pipe avec ses options, et ) referme l'appel.
Note le pluriel dans le nom de la méthode : useGlobalPipes accepte plusieurs pipes séparés par des virgules. En pratique, tu n'en passes presque toujours qu'un - ValidationPipe - car les autres, tel ParseIntPipe, n'ont de sens que sur des paramètres précis.
Quatre modes, du plus doux
La configuration du pipe n'est pas un jeu d'interrupteurs indépendants, mais une échelle de sévérité. Il vaut la peine de la connaître en entier, car choisir un barreau est une décision sur la manière dont ton API traite les données inconnues.
- Pas de
ValidationPipe- aucune validation. N'importe quel objet JSON parvient au handler. Les annotations de types dans le code sont alors un vœu, non une garantie. ValidationPipe()- validation de base. Les règles des décorateurs s'appliquent, mais les champs étrangers au DTO passent intacts.whitelist: true- suppression des champs inconnus.whitelist+forbidNonWhitelisted- rejet des requêtes.
Une seule chose sépare les deux derniers, et elle mérite d'être retenue exactement. L'option whitelist: true supprime automatiquement des données entrantes les champs sans décorateurs de validation. Elle n'ajoute pas de fond blanc aux réponses, ne crée pas de liste d'adresses IP autorisées et ne désactive pas la validation sur certains endpoints - elle retire simplement ce que tu n'as pas décrit dans le DTO.
La différence avec forbidNonWhitelisted: true tient à ceci : whitelist supprime les champs inconnus en silence, tandis que forbidNonWhitelisted renvoie une erreur 400. Les deux ne sont pas identiques, forbidNonWhitelisted ne désactive rien et n'ajoute aucun champ aux logs du serveur.
1// Le DTO ne decrit que : name, cohortId
2// Le client envoie : { name: 'Marcus', cohortId: 3, isAdmin: true }
3
4// ValidationPipe() -> le service recoit isAdmin: true
5// whitelist: true -> le service recoit { name, cohortId }
6// whitelist + forbidNonWhitelisted -> 400 Bad RequestLe champ isAdmin de cet exemple n'est pas fortuit. Sans whitelist, il atteint le service, et si quelqu'un écrit plus loin Object.assign(user, dto), le client vient de s'octroyer des privilèges. C'est la plus ancienne façon de s'emparer d'un compte dans les applications qui font confiance à leur entrée.
Quel mode choisir ? forbidNonWhitelisted est plus strict et par là plus honnête : le client apprend qu'il a envoyé quelque chose d'inattendu, au lieu de croire que tu as enregistré son champ. Le simple whitelist s'avère parfois plus commode sur une API publique où d'anciens clients envoient encore des champs retirés depuis.
transform
La dernière option de la panoplie de production concerne les types. transform: true fait parvenir au handler une instance de la classe DTO plutôt qu'un objet ordinaire - et convertit au passage les types simples. Sans elle, @Param('id') id: number te donne la chaîne '7', quoique le type dise number, car tout arrive d'une URL sous forme de texte.
C'est aussi la condition pour que @Type et @Transform de class-transformer agissent : sans transform: true, ils demeurent des annotations sans effet.
Sur un endpoint isolé
Quand un endpoint réclame d'autres règles que le reste, le pipe se branche localement par @UsePipes(new ValidationPipe({ ... })) au-dessus de la méthode. Un pipe local l'emporte sur le pipe global.
Recours-y rarement. Une configuration de validation dispersée dans les contrôleurs cesse vite d'être lisible, et répondre à « cet endpoint rejette-t-il les champs superflus ? » commence à exiger la lecture de trois fichiers.
Récapitulatif
Un bordereau ne vérifie rien de lui-même :
- le rôle principal de
ValidationPipeest d'exécuter automatiquement la validation des décorateurs de class-validator - non le chiffrement, non la compression, non la génération de Swagger, - enregistrement global :
app→.useGlobalPipes(→new ValidationPipe({ ... })→), - les modes, du moins restrictif : pas de
ValidationPipe→ValidationPipe()→whitelist: true→whitelist+forbidNonWhitelisted, whitelist: truesupprime des données entrantes les champs sans décorateurs de validation - rien à voir avec des adresses IP ni avec la désactivation de la validation,whitelistsupprime les champs inconnus en silence,forbidNonWhitelistedrenvoie une erreur400,- un champ non retiré tel qu'
isAdminest le chemin le plus simple pour s'octroyer des privilèges, transform: truedonne une instance de DTO et convertit les types simples ; sans elle,@Typeet@Transformrestent sans effet,@UsePipesau-dessus d'une méthode l'emporte sur la configuration globale - use-en avec parcimonie.
Dans la prochaine leçon, nous aborderons class-transformer - ce qu'il advient des données dans l'autre sens, à leur sortie de l'API. En attendant, retiens ceci : les décorateurs décrivent, le pipe exécute. Sans lui, tu détiens une documentation très précise de règles que personne n'applique.
Code de cette leçon : src/validation-pipe.ts
1// ValidationPipe - Configuration du poste de contrôle
2import { NestFactory } from '@nestjs/core';
3import { ValidationPipe } from '@nestjs/common';
4
5// TODO : Configurer ValidationPipe globalement
6async function bootstrap() {
7 const app = await NestFactory.create(AppModule);
8
9 // TODO : Ajouter app.useGlobalPipes avec ValidationPipe
10 // Options requises :
11 // - whitelist: true
12 // - forbidNonWhitelisted: true
13 // - transform: true
14
15 await app.listen(3000);
16 console.log('Porte de l\'Imperium ouverte sur le port 3000');
17}
18
19// Exemple d'utilisation au niveau d'une 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 : Ajouter @UsePipes(new ValidationPipe({ whitelist: true }))
36 @Post()
37 create(@Body() dto: CreateSoldierDto) {
38 return { message: 'Soldat recruté !', data: dto };
39 }
40}
41
42console.log('ValidationPipe configuré !');
43Tu as repéré une erreur dans cette leçon ?
Teste tes connaissances
Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.
1. Quel est le rôle principal de ValidationPipe dans NestJS ?
2. Que fait l'option whitelist: true dans ValidationPipe ?
Voici 2 des 3 questions de cette leçon. Réponds aux autres dans le jeu.
Tâches pratiques dans le jeu
- Éditeur de code
Complète la configuration de ValidationPipe dans la fonction bootstrap
- Tri horizontal
Range les éléments de la configuration du ValidationPipe global dans le bon ordre :
- Éditeur de code
Complète les commentaires décrivant le comportement de chaque mode de ValidationPipe
- Tri vertical
Range les modes de ValidationPipe du moins au plus restrictif :