Avez-vous une question? (216) 97 656 803 hajjriadh@gmail.com
NestJS

Récupérer le corps de la requête (@Body)

Cette leçon explique comment récupérer les données envoyées dans le corps d'une requête POST, PUT ou PATCH avec le décorateur @Body : typage avec une classe DTO, lecture d'une seule propriété, mise à ...

0 chapitres 0 QCM Accès gratuit
Récupérer le corps de la requête (@Body)

À propos de ce cours

Cette leçon explique comment récupérer les données envoyées dans le corps d'une requête POST, PUT ou PATCH avec le décorateur @Body : typage avec une classe DTO, lecture d'une seule propriété, mise à jour partielle. Elle montre par des requêtes réelles ce que NestJS fait des différents formats (JSON, formulaire, texte brut), comment il réagit à un JSON mal formé (400) ou à un corps trop volumineux (413) et comment augmenter cette limite. Elle démontre surtout que, sans validation, des données incorrectes sont acceptées, ce qui prépare le chapitre 08.

Objectifs de la leçon

  • Lire le corps d'une requête avec @Body et le typer avec un DTO.
  • Lire une seule propriété du corps.
  • Savoir ce que deviennent les différents formats envoyés.
  • Comprendre pourquoi le typage ne suffit pas à valider les données.
  • Ajuster la taille maximale du corps.
Le corps d'une requête

Les requêtes POST, PUT et PATCH transportent des données dans leur corps (body), le plus souvent en JSON, annoncé par l'en-tête Content-Type: application/json. NestJS lit et convertit automatiquement ce corps ; le décorateur @Body() le donne à votre méthode.

Typer le corps avec un DTO

Un DTO (Data Transfer Object) décrit la forme des données attendues. Dans NestJS, c'est une classe (et non une interface), pour qu'elle existe encore à l'exécution : au chapitre 08, on y ajoutera des règles de validation.

📄 src/inscriptions/creer-inscription.dto.ts

export class CreerInscriptionDto {
  nom: string;
  prenom: string;
  email: string;
  formationId: number;
  options?: { alternance: boolean; transport: boolean };
}

📄 src/inscriptions/inscriptions.controller.ts

import { Body, Controller, Get, Param, ParseIntPipe, Patch, Post } from '@nestjs/common';
import { CreerInscriptionDto } from './creer-inscription.dto';

interface Inscription extends CreerInscriptionDto {
  id: number;
  date: string;
}

@Controller('inscriptions')
export class InscriptionsController {
  private inscriptions: Inscription[] = [];

  @Post()
  create(@Body() dto: CreerInscriptionDto) {
    const inscription = { id: this.inscriptions.length + 1, ...dto, date: new Date().toISOString().slice(0, 10) };
    this.inscriptions.push(inscription);
    return inscription;
  }

  @Post('verifier-email')
  verifier(@Body('email') email: string) {          // une seule propriété du corps
    return { email, existe: this.inscriptions.some((i) => i.email === email) };
  }

  @Patch(':id')
  update(@Param('id', ParseIntPipe) id: number, @Body() modifications: Partial<CreerInscriptionDto>) {
    const i = this.inscriptions.find((x) => x.id === id);
    if (!i) return { message: `Inscription ${id} introuvable` };
    Object.assign(i, modifications);
    return i;
  }

  @Get()
  findAll() {
    return this.inscriptions;
  }
}

Requêtes envoyées et réponses réellement obtenues :

POST /api/inscriptions → 201 {"id":1,"nom":"Ben Ali","prenom":"Sami","email":"sami@exemple.tn","formationId":1,"options":{"alternance":true,"transport":false},"date":"…"}
POST /api/inscriptions/verifier-email → 201 {"email":"sami@exemple.tn","existe":true}
PATCH /api/inscriptions/1 → 200 {"id":1,"nom":"Ben Ali","prenom":"Sami","email":"sami@exemple.tn","formationId":2,"options":{"alternance":true,"transport":false},"date":"…"}
  • @Body() dto: CreerInscriptionDto: tout le corps, y compris les objets imbriqués (options).
  • @Body('email'): une seule propriété.
  • Pour une mise à jour partielle, Partial<CreerInscriptionDto> (leçon 4.6) rend toutes les propriétés facultatives, et Object.assign ne remplace que celles envoyées.
Ce que devient le corps selon son format

Ce que devient le corps de la requête dans @Body

JSON et formulaires sont lus ; les autres formats sont ignorés ; JSON invalide et corps trop gros sont rejetés.

Quatre envois particuliers et leurs réponses (JSON mal formé, formulaire, texte brut, corps de 150 Ko) :

POST /api/inscriptions → 201 {"id":3,"nom":"Mansouri","prenom":"Lina","formationId":"1","date":"…"}
POST /api/inscriptions → 201 {"id":4,"date":"…"}
POST /api/inscriptions → 413 {"statusCode":413,"message":"request entity too large"}
  • JSON mal formé: NestJS répond 400 avec le message d'analyse, avant même d'appeler votre méthode.
  • Formulaire HTML (application/x-www-form-urlencoded) : lu aussi, mais toutes les valeurs sont des chaînes (formationId vaut "1").
  • Autre format (text/plain…) : le corps n'est pas lu, @Body() vaut undefined. Le client doit envoyer l'en-tête Content-Type: application/json.
  • Corps de plus de 100 Ko: 413 request entity too large.
Le typage ne valide rien !

Regardez cette requête, réellement acceptée par l'API :

Requête et réponse :

POST /api/inscriptions → 201 {"id":2,"nom":42,"email":"pas-un-email","admin":true,"date":"…"}

Le nom est un nombre, l'e-mail est invalide, le prénom et la formation manquent, et un champ admin: true inattendu a été enregistré. Pourtant le DTO déclare nom: string… Les types TypeScript sont effacés à la compilation (leçon 4.4) : à l'exécution, NestJS ne vérifie rien. C'est une faille sérieuse (imaginez un champ role: 'admin' injecté dans un utilisateur). Le chapitre 08 la comble avec ValidationPipe et les décorateurs de class-validator:

// Aperçu du chapitre 08
export class CreerInscriptionDto {
  @IsString() @MinLength(2) nom: string;
  @IsString() prenom: string;
  @IsEmail() email: string;
  @IsInt() formationId: number;
}
// + app.useGlobalPipes(new ValidationPipe({ whitelist: true }))  → 400 et champs inconnus supprimés

En attendant, ne faites jamais confiance au corps reçu : vérifiez dans le service ce qui est indispensable.

Augmenter la taille maximale

Pour un import de données volumineux, la limite de 100 Ko peut être relevée dans main.ts:

import { NestExpressApplication } from '@nestjs/platform-express';

const app = await NestFactory.create<NestExpressApplication>(AppModule);
app.useBodyParser('json', { limit: '1mb' });

Envoi de 5 000 lignes (environ 250 Ko) avec la limite à 1 Mo :

POST /import → 201 {"recues":5000}

Ne relevez pas la limite sans raison : un corps énorme consomme mémoire et temps de traitement, et peut servir à surcharger le serveur. Pour les fichiers, on utilise un envoi multipart dédié (chapitre 15), pas du JSON.

Activités

Activité 1 Créer des formations par POST

Dans le CRUD des formations, créez un DTO CreerFormationDto (intitulé, heures, publiée) et utilisez-le dans POST et PATCH (avec Partial). Testez avec un fichier .http, en oubliant volontairement une fois la ligne Content-Type. Que se passe-t-il ?

Voir le corrigé

Sans Content-Type: application/json, REST Client envoie le corps comme texte : @Body() vaut undefined et la formation créée ne contient que son identifiant. Dans un fichier .http, la ligne Content-Type et la ligne vide avant le corps sont indispensables.

Activité 2 Une propriété du corps

Écrivez POST /api/formations/:id/publier qui lit uniquement @Body('publiee') (booléen) et met à jour la formation. Que reçoit la méthode si le client envoie { "publiee": "oui" }? Comment s'en protéger dès maintenant ?

Voir le corrigé
@Post(':id/publier')
@HttpCode(200)
publier(@Param('id', ParseIntPipe) id: number, @Body('publiee', ParseBoolPipe) publiee: boolean) {
  return this.formationsService.update(id, { publiee });
}

Sans pipe, la méthode reçoit la chaîne « oui ». Avec ParseBoolPipe, qui fonctionne aussi sur @Body('…'), la valeur doit être true, false, 'true' ou 'false', sinon 400. @HttpCode(200) évite le 201, peu logique pour une action qui ne crée rien.

Activité 3 Constater l'absence de validation

Envoyez à POST /api/formations: { "intitule": "", "heures": -50, "publiee": "peut-être", "supprimee": true }. Relevez tout ce qui ne va pas dans la formation enregistrée. Écrivez dans le service une vérification minimale qui lève BadRequestException pour l'intitulé vide et les heures négatives.

Voir le corrigé
create(donnees: CreerFormationDto): Formation {
  if (!donnees?.intitule?.trim()) throw new BadRequestException('L\'intitulé est obligatoire');
  if (typeof donnees.heures !== 'number' || donnees.heures <= 0) {
    throw new BadRequestException('Le nombre d\'heures doit être un nombre positif');
  }
  const f = { id: this.prochainId++, intitule: donnees.intitule.trim(), heures: donnees.heures, publiee: donnees.publiee === true };
  this.formations.push(f);
  return f;
}

Quatre problèmes : intitulé vide, heures négatives, booléen remplacé par une chaîne, champ inconnu supprimee enregistré. La vérification manuelle protège, mais devient vite lourde et répétitive : c'est exactement ce que ValidationPipe automatisera au chapitre 08. Remarquez aussi qu'on construit l'objet champ par champ au lieu de recopier ...donnees: les champs inconnus sont ainsi ignorés.

Activité 4 Formulaire HTML

Créez un fichier formulaire.html contenant un formulaire method="post" dont l'action est http://localhost:3000/api/inscriptions, avec des champs nom, prénom, e-mail et formation. Envoyez-le depuis le navigateur. Quel est le type de formationId reçu ? Pourquoi est-ce un problème ?

Voir le corrigé

Le formulaire est envoyé en application/x-www-form-urlencoded: NestJS le lit, mais formationId est la chaîne "1". Une comparaison stricte (f.id === dto.formationId) échouera. ValidationPipe avec l'option transform convertira automatiquement les types (chapitre 08).

À retenir

  • @Body() lit tout le corps, @Body('champ') une propriété ; le corps JSON est converti automatiquement.
  • Les DTO sont des classes : ils serviront à la validation.
  • JSON mal formé → 400 ; corps de plus de 100 Ko → 413 ; format non JSON → undefined.
  • Les types TypeScript ne valident rien à l'exécution : données incorrectes et champs inconnus passent.
  • app.useBodyParser('json', { limit }) ajuste la taille maximale, avec prudence.