NestJS : valider les entrées de son API avec les DTO
· 7 min de lecture
NestJS peut vérifier chaque requête reçue par votre API. Les données fausses sont refusées avant d’atteindre votre code. L’article montre comment, avec ValidationPipe et class-validator.
En bref
Une application ne doit jamais croire les données qu’on lui envoie. L’article montre comment NestJS vérifie chaque demande et refuse les données incorrectes, avant qu’elles ne causent des erreurs.
Un mot technique vous échappe ? Le glossaire du développement web l’explique simplement.
Une API ne devrait jamais faire confiance à ce qu’elle reçoit : il faut valider toutes ses entrées. Avec NestJS, la validation se déclare au même endroit que le contrat : la classe DTO (Data Transfer Object). Les décorateurs de class-validator décrivent les règles, et un pipe global les applique à chaque requête.
Déclarer le contrat
import { IsEmail, IsInt, IsOptional, IsString, Length, Max, Min } from 'class-validator';
export class CreateUserDto {
@IsEmail()
email: string;
@IsString()
@Length(2, 50)
name: string;
@IsOptional()
@IsInt()
@Min(18)
@Max(120)
age?: number;
}Activer la validation globalement
Le ValidationPipe s’enregistre une seule fois, dans main.ts. Trois options font toute la différence :
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}),
);- whitelist supprime les propriétés sans décorateur : un client ne peut pas glisser un champ isAdmin: true dans le corps de la requête.
- forbidNonWhitelisted va plus loin. Au lieu d’ignorer ces champs sans rien dire, il renvoie une erreur 400.
- transform convertit le corps en véritable instance du DTO, et les paramètres de route dans le type déclaré : un :id devient un number.
Ce que reçoit le contrôleur
@Post()
create(@Body() dto: CreateUserDto) {
// dto is already validated and typed
return this.usersService.create(dto);
}Une requête invalide n’atteint jamais cette méthode. NestJS répond tout de suite 400 Bad Request, avec la liste des règles non respectées. Le front-end peut afficher ces messages tels quels.
Valider les paramètres de route
Le corps de la requête n’est pas la seule entrée à contrôler. Les pipes intégrés comme ParseUUIDPipe ou ParseIntPipe vérifient les paramètres de route et renvoient une erreur 400 si le format ne correspond pas :
@Get(':id')
findOne(@Param('id', ParseUUIDPipe) id: string) {
return this.usersService.findOne(id);
}Les objets imbriqués
Par défaut, class-validator ne vérifie pas un objet placé dans un autre. Ajoutez @ValidateNested pour qu’il le fasse. Donnez aussi la classe à créer avec @Type, de class-transformer :
export class CreateOrderDto {
@ValidateNested({ each: true })
@Type(() => OrderLineDto)
@ArrayMinSize(1)
lines: OrderLineDto[];
}Éviter la duplication
Pour une mise à jour partielle, inutile de réécrire le DTO : PartialType (de @nestjs/mapped-types, ou de @nestjs/swagger si l’API est documentée) rend tous les champs optionnels en conservant leurs règles. PickType et OmitType permettent de n’en garder qu’une partie.
export class UpdateUserDto extends PartialType(CreateUserDto) {}La validation devient déclarative, écrite une fois et lisible d’un coup d’œil dans le DTO. C’est aussi une première ligne de défense contre l’injection de champs inattendus. Une fois les données validées, des types bien choisis (voir « TypeScript : 5 techniques de typage qui évitent des bugs ») prolongent cette rigueur dans le reste du code.