Cours NestJS · Module 1: Bases de NestJS
DTO et validation de base
Dans cette leçon8
Ave, gardien de la porte ! Le consul Caesar.js sait que la sécurité de l'Empire commence par le contrôle de ce qui entre par les portes. Nous ne pouvons pas laisser des données invalides pénétrer dans notre système - c'est comme laisser des espions entrer dans le camp de la légion ! Il est temps d'apprendre les DTO et la validation - les boucliers protecteurs de notre API.
Que sont les DTO ?
Un DTO (Data Transfer Object) est une classe qui définit la forme des données envoyées vers et depuis notre API. Considère un DTO comme un formulaire de recrutement de la légion - chaque nouveau légionnaire doit remplir les champs appropriés avant d'être accepté.
Sans DTO, notre contrôleur accepte n'importe quoi - c'est comme une porte ouverte sans gardes :
1// SANS DTO - dangereux !
2@Post()
3create(@Body() body: any) {
4 // body peut contenir n'importe quoi... dangereux !
5 return this.legionariesService.create(body);
6}Avec un DTO, nous définissons précisément quelles données nous acceptons :
1// AVEC DTO - sécurisé comme un fort romain !
2@Post()
3create(@Body() createLegionaryDto: CreateLegionaryDto) {
4 return this.legionariesService.create(createLegionaryDto);
5}Créer des classes DTO
Créons un DTO pour notre système de légionnaires :
1// dto/create-legionary.dto.ts
2export class CreateLegionaryDto {
3 name: string;
4 rank: string;
5 province: string;
6 email: string;
7 experience: number;
8}Cependant, ceci n'est que la forme des données - cela ne vérifie pas encore si les données sont valides. C'est là qu'intervient la validation !
Installer class-validator et class-transformer
Pour ajouter la validation, nous avons besoin de deux bibliothèques :
1npm install class-validator class-transformer- class-validator - fournit les décorateurs de validation (@IsString, @IsNumber, etc.)
- class-transformer - transforme les objets JSON simples en instances de classes DTO
Décorateurs de validation
Ajoutons maintenant la validation à notre DTO :
1// dto/create-legionary.dto.ts
2import {
3 IsString, IsNumber, IsEmail,
4 MinLength, MaxLength, Min, Max,
5 IsNotEmpty, IsOptional, IsIn
6} from 'class-validator';
7
8export class CreateLegionaryDto {
9 @IsString()
10 @IsNotEmpty()
11 @MinLength(2)
12 @MaxLength(50)
13 name: string;
14
15 @IsString()
16 @IsIn(['Miles', 'Optio', 'Centurio', 'Tribunus', 'Legatus'])
17 rank: string;
18
19 @IsString()
20 @IsNotEmpty()
21 province: string;
22
23 @IsEmail()
24 email: string;
25
26 @IsNumber()
27 @Min(0)
28 @Max(30)
29 experience: number;
30}Voici les décorateurs de validation les plus couramment utilisés :
| Décorateur | Description |
|---|---|
@IsString() | Vérifie si la valeur est une chaîne de caractères |
@IsNumber() | Vérifie si la valeur est un nombre |
@IsEmail() | Vérifie si la valeur est un e-mail valide |
@IsNotEmpty() | Vérifie si le champ n'est pas vide |
@MinLength(n) | Longueur minimale du texte |
@MaxLength(n) | Longueur maximale du texte |
@Min(n) | Valeur numérique minimale |
@Max(n) | Valeur numérique maximale |
@IsOptional() | Le champ est optionnel |
@IsIn([...]) | La valeur doit être l'une des options données |
DTO de mise à jour - PartialType
Pour les mises à jour partielles (PATCH), nous pouvons utiliser PartialType, qui rend tous les champs optionnels :
1// dto/update-legionary.dto.ts
2import { PartialType } from '@nestjs/mapped-types';
3import { CreateLegionaryDto } from './create-legionary.dto';
4
5export class UpdateLegionaryDto extends PartialType(CreateLegionaryDto) {}
6// Maintenant, tous les champs sont optionnels !PartialType vient d'un paquet distinct, que tu installes avec npm i @nestjs/mapped-types. Il crée automatiquement une nouvelle classe où chaque champ de CreateLegionaryDto devient optionnel. Grâce à cela, nous n'avons pas besoin d'écrire un DTO séparé avec @IsOptional() sur chaque champ.
ValidationPipe - validation globale
Pour que la validation fonctionne, nous devons activer ValidationPipe dans notre application :
1// main.ts
2import { NestFactory } from '@nestjs/core';
3import { ValidationPipe } from '@nestjs/common';
4import { AppModule } from './app.module';
5
6async function bootstrap() {
7 const app = await NestFactory.create(AppModule);
8
9 app.useGlobalPipes(new ValidationPipe({
10 whitelist: true, // Supprime les champs qui ne sont pas dans le DTO
11 forbidNonWhitelisted: true, // Lève une erreur quand des champs inconnus sont présents
12 transform: true, // Conversion automatique des types
13 }));
14
15 await app.listen(3000);
16}
17bootstrap();Trois options clés de ValidationPipe :
- whitelist: true - supprime automatiquement les champs absents du DTO (comme un garde rejetant les colis suspects)
- forbidNonWhitelisted: true - au lieu de supprimer silencieusement les champs inconnus, lève une erreur (contrôle plus strict)
- transform: true - convertit automatiquement les données dans les types appropriés (la chaîne "5" en nombre 5)
Exemple complet de contrôleur avec DTO
1@Controller('legionaries')
2export class LegionariesController {
3 constructor(private readonly legionariesService: LegionariesService) {}
4
5 @Post()
6 create(@Body() createDto: CreateLegionaryDto) {
7 return this.legionariesService.create(createDto);
8 }
9
10 @Patch(':id')
11 update(
12 @Param('id') id: string,
13 @Body() updateDto: UpdateLegionaryDto,
14 ) {
15 return this.legionariesService.update(Number(id), updateDto);
16 }
17}Lorsque quelqu'un envoie des données invalides, ValidationPipe renverra automatiquement une erreur lisible :
1{
2 "statusCode": 400,
3 "message": [
4 "name must be longer than or equal to 2 characters",
5 "email must be an email",
6 "experience must not be greater than 30"
7 ],
8 "error": "Bad Request"
9}Pourquoi les DTO sont-ils importants ?
- Sécurité - ils bloquent les données indésirables avant qu'elles n'atteignent la logique métier
- Documentation - ils définissent clairement le contrat de l'API
- Validation - vérification automatique de la validité des données
- Typage - TypeScript connaît la forme des données dans toute l'application
- Séparation - ils séparent la couche de transport de la logique métier
Les DTO et la validation sont le bouclier et l'épée de notre Empire - ils protègent les portes de l'API et garantissent que seules des données valides entrent dans le système. Tout bon bâtisseur d'Empire sait que des murs solides sont le fondement !
Code de cette leçon : src/dto-validation.ts
1// DTO et validation - Boucliers protecteurs de l'Imperium
2import { IsString, IsNumber, IsEmail, MinLength, MaxLength, Min, Max, IsNotEmpty, IsOptional, IsIn } from 'class-validator';
3
4console.log("DTO et validation - gardien de la porte de l'imperium !");
5
6// ===========================================
7// 1. DTO - Data Transfer Object
8// ===========================================
9
10// La classe DTO définit la forme des données
11class CreateLegionaryDto {
12 // @IsString()
13 // @IsNotEmpty()
14 // @MinLength(2)
15 // @MaxLength(50)
16 name: string;
17
18 // @IsString()
19 // @IsIn(['Miles', 'Optio', 'Centurio', 'Tribunus', 'Legatus'])
20 rank: string;
21
22 // @IsString()
23 // @IsNotEmpty()
24 province: string;
25
26 // @IsEmail()
27 email: string;
28
29 // @IsNumber()
30 // @Min(0)
31 // @Max(30)
32 experience: number;
33}
34
35console.log("=== CREATE LEGIONARY DTO ===");
36console.log("name: @IsString, @IsNotEmpty, @MinLength(2)");
37console.log("rank: @IsString, @IsIn([...])");
38console.log("province: @IsString, @IsNotEmpty");
39console.log("email: @IsEmail");
40console.log("experience: @IsNumber, @Min(0), @Max(30)");
41
42// ===========================================
43// 2. Décorateurs de validation
44// ===========================================
45
46console.log("\n=== DÉCORATEURS DE VALIDATION ===");
47console.log("@IsString() - vérifie si c'est du texte");
48console.log("@IsNumber() - vérifie si c'est un nombre");
49console.log("@IsEmail() - vérifie si c'est un e-mail");
50console.log("@IsNotEmpty() - vérifie si ce n'est pas vide");
51console.log("@MinLength(n) - longueur minimale du texte");
52console.log("@MaxLength(n) - longueur maximale du texte");
53console.log("@Min(n) - valeur numérique minimale");
54console.log("@Max(n) - valeur numérique maximale");
55console.log("@IsOptional() - champ optionnel");
56console.log("@IsIn([...]) - valeur issue d'une liste");
57
58// ===========================================
59// 3. DTO de mise à jour avec PartialType
60// ===========================================
61
62// import { PartialType } from '@nestjs/mapped-types';
63// class UpdateLegionaryDto extends PartialType(CreateLegionaryDto) {}
64// -> Tous les champs deviennent optionnels !
65
66class UpdateLegionaryDto {
67 name?: string;
68 rank?: string;
69 province?: string;
70 email?: string;
71 experience?: number;
72}
73
74console.log("\n=== UPDATE DTO (PartialType) ===");
75console.log("PartialType rend tous les champs optionnels");
76console.log("Idéal pour PATCH - mise à jour partielle");
77
78// ===========================================
79// 4. ValidationPipe - validation globale
80// ===========================================
81
82// Dans main.ts :
83// app.useGlobalPipes(new ValidationPipe({
84// whitelist: true, // Retire les champs inconnus
85// forbidNonWhitelisted: true, // Erreur sur les champs inconnus
86// transform: true, // Conversion des types
87// }));
88
89console.log("\n=== VALIDATION PIPE ===");
90console.log("whitelist: true -> retire les champs hors du DTO");
91console.log("forbidNonWhitelisted: true -> erreur sur les champs inconnus");
92console.log("transform: true -> conversion des types (string -> number)");
93
94// ===========================================
95// 5. Exemple d'utilisation dans un contrôleur
96// ===========================================
97
98console.log("\n=== CONTRÔLEUR AVEC DTO ===");
99console.log("@Post()");
100console.log("create(@Body() createDto: CreateLegionaryDto) { ... }");
101console.log("");
102console.log("@Patch(':id')");
103console.log("update(@Param('id') id, @Body() updateDto: UpdateLegionaryDto) { ... }");
104
105// ===========================================
106// 6. Exemple de réponse d'erreur de validation
107// ===========================================
108
109const validationError = {
110 statusCode: 400,
111 message: [
112 "name must be longer than or equal to 2 characters",
113 "email must be an email",
114 "experience must not be greater than 30"
115 ],
116 error: "Bad Request"
117};
118
119console.log("\n=== EXEMPLE D'ERREUR DE VALIDATION ===");
120console.log(JSON.stringify(validationError, null, 2));
121
122console.log("\n=== RÉSUMÉ ===");
123console.log("1. DTO = forme des données (contrat de l'API)");
124console.log("2. class-validator = décorateurs de validation");
125console.log("3. class-transformer = conversion d'objets");
126console.log("4. ValidationPipe = validation automatique");
127console.log("5. PartialType = champs optionnels (pour PATCH)");
128Tu 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. Que signifie l'abréviation DTO dans le contexte de NestJS ?
2. Quel décorateur class-validator vérifie qu'une valeur est une adresse e-mail valide ?
Tâches pratiques dans le jeu
- Tri vertical
Remets dans l'ordre les étapes pour ajouter la validation d'un DTO dans NestJS :
- Éditeur de code
Écris une classe CreateLegionaryDto avec les champs name (@IsString, @IsNotEmpty) et email (@IsEmail)
- Clique dans l'ordre
Remets dans l'ordre la syntaxe pour activer ValidationPipe dans main.ts :
- Tri horizontal
Remets dans l'ordre les éléments pour créer UpdateDto avec PartialType :
- Éditeur de code
Dans main.ts, écris une fonction setupValidation(app) qui active la validation globale de l'application : elle appelle app.useGlobalPipes() avec un nouveau ValidationPipe dont les options whitelist et transform valent true.