Cours NestJS · Module 11: Swagger et OpenAPI
Décorateurs de contrôleurs - les sceaux sur les décrets
Dans cette leçon5
Un décret sans sceau n'est qu'une feuille. On ignore qui l'a émis, ce qu'il concerne et s'il est seulement en vigueur - et un fonctionnaire de province doit trancher cela avant d'agir.
Un endpoint sans description est exactement pareil. Swagger lira tout seul le chemin et la méthode HTTP dans le code, mais il ne devinera pas ce que fait l'endpoint, quels paramètres sont obligatoires, ni ce que signifie le code qu'il renvoie. Les décorateurs du paquet @nestjs/swagger ajoutent ce que le code seul ne montre pas.
Une remarque importante d'emblée : tous ces décorateurs ne font que décrire. Aucun ne change le comportement de l'application - un endpoint dépourvu d'@ApiOperation fonctionne exactement comme avant, il est simplement sans nom dans la documentation.
@ApiTags et @ApiOperation
Les deux premiers sceaux répondent aux questions « où trouver ceci » et « que fait-il » :
1@ApiTags('Provinces')
2@Controller('provinces')
3export class ProvincesController {
4 @ApiOperation({
5 summary: 'Liste des provinces',
6 description: 'Renvoie toutes les provinces de l Empire avec leurs gouverneurs.',
7 })
8 @Get()
9 findAll() {
10 return this.provincesService.findAll();
11 }
12}@ApiTags() groupe les endpoints en sections - dans Swagger UI, cela devient un groupe repliable. Tu le places au-dessus de la classe du contrôleur, car le groupement vaut pour toutes ses méthodes à la fois. On peut énumérer plusieurs tags, séparés par des virgules, quand un contrôleur relève de deux domaines.
@ApiOperation() accepte deux propriétés principales : summary et description. Non url et method - NestJS les lit dans @Get et @Controller. Non host et port - c'est la configuration du serveur, non la description d'un endpoint. Ni controller ni service - ce sont des noms de classes dont la documentation n'a que faire.
La différence entre les deux est pratique : summary est la phrase unique affichée dans la liste des endpoints, description le texte plus long qui se déplie au clic. Si tu ne dois en écrire qu'un, écris le summary.
@ApiParam contre @ApiQuery
Ces deux-là se confondent le plus souvent, car tous deux décrivent « quelque chose qui arrive dans l'adresse ». C'est la place dans l'URL qui tranche :
1@ApiParam({ name: 'id', description: 'Identifiant de la province' })
2@Get(':id')
3findOne(@Param('id') id: string) {
4 return this.provincesService.findOne(id);
5}@ApiParam() décrit les paramètres de route dans le chemin de l'URL - ceux qu'on écrit avec deux-points dans la route, comme :id, et qui font partie de l'adresse. Dans /provinces/7, le sept est un paramètre de route.
@ApiQuery() décrit les paramètres de requête - ceux qui suivent le signe ? dans l'URL. Dans /provinces?region=Gallia&page=2, les paramètres de requête sont region et page. Il n'exécute aucune requête sur la base de données, ne définit aucun schéma GraphQL et ne crée aucun query builder pour un ORM - le mot « query » ne désigne ici qu'une partie de l'adresse.
Les autres emplacements ont leurs propres décorateurs : les en-têtes HTTP sont décrits par @ApiHeader, et le corps de la requête par @ApiBody. Quatre emplacements distincts, quatre décorateurs distincts ; une confusion ne produit aucune erreur, seulement une documentation décrivant ce qui n'est pas là.
La forme d'@ApiQuery
Les paramètres de requête sont souvent facultatifs, et c'est précisément ce que la documentation doit consigner :
1@ApiQuery({
2 name: 'province',
3 required: false,
4})
5@Get('search')
6search(@Query('province') province?: string) {
7 return this.provincesService.search(province);
8}L'ordre d'écriture est fixe : @ApiQuery({ ouvre le décorateur, name: 'province', donne le nom du paramètre - le seul champ obligatoire - required: false le marque comme facultatif, et }) referme.
required vaut true par défaut : le caractère facultatif doit donc être déclaré explicitement. L'omettre est un détail qui coûte le temps d'autrui : dans Swagger UI, le paramètre apparaît comme requis, et quelqu'un construit un client qui envoie region à chaque appel.
L'ordre sur un endpoint
Réunis, les décorateurs vont du plus général au plus particulier :
1@ApiTags('Provinces')
2@Controller('provinces')
3export class ProvincesController {
4 @ApiOperation({ summary: 'Details de la province' })
5 @ApiResponse({ status: 200, description: 'Province trouvee' })
6 @ApiResponse({ status: 404, description: 'Aucune province de ce nom' })
7 @ApiParam({ name: 'id', description: 'Identifiant de la province' })
8 @ApiBearerAuth('JWT-auth')
9 @Get(':id')
10 findOne(@Param('id') id: string) {
11 return this.provincesService.findOne(id);
12 }
13}Cela se lit de haut en bas : @ApiTags('Name') sur la classe du contrôleur couvre toutes les méthodes, puis au-dessus de la méthode précise @ApiOperation({ summary }) dit ce qu'elle fait, ensuite @ApiResponse({ status }) énumère les réponses possibles, et enfin s'exécute le corps de la méthode.
@ApiResponse() reçoit un code de statut et une description ; on peut le répéter, une fois par réponse possible. Dans les leçons à venir, nous le remplacerons par des équivalents plus brefs tels qu'@ApiOkResponse.
@ApiBearerAuth('JWT-auth') marque l'endpoint comme protégé - un champ de token apparaît à côté dans Swagger UI, et le nom fourni doit correspondre à la configuration d'addBearerAuth.
Récapitulatif
Un décret sans sceau n'est qu'une feuille :
- les décorateurs Swagger ne font que décrire - ils ne changent pas le comportement d'un endpoint,
@ApiTags()groupe les endpoints et se place au-dessus de la classe du contrôleur,@ApiOperation()acceptesummaryetdescription- nonurl/method, nonhost/port, noncontroller/service,@ApiParam()décrit les paramètres de route dans le chemin de l'URL (ceux à deux-points, comme:id),@ApiQuery()décrit les paramètres de requête après le signe?- il n'interroge pas la base, ne définit pas de GraphQL, ne construit pas de requêtes ORM,- les en-têtes sont décrits par
@ApiHeader, le corps de la requête par@ApiBody, - la forme d'
@ApiQuery:@ApiQuery({→name: 'province',→required: false→}); sansrequired: false, un paramètre passe pour obligatoire, - l'ordre :
@ApiTags('Name')sur la classe →@ApiOperation({ summary })au-dessus de la méthode →@ApiResponse({ status })au-dessus de la méthode → le corps de la méthode, @ApiBearerAuth('JWT-auth')marque un endpoint protégé par token.
Dans la prochaine leçon, nous descendrons d'un niveau - vers la documentation des DTO eux-mêmes, c'est-à-dire de ce qui siège réellement dans ces requêtes et ces réponses. En attendant, retiens ceci : la documentation décrit ce que tu as écrit dans le décorateur, non ce que fait le code. Un écart entre les deux est pire qu'une absence de documentation, car au premier, quelqu'un croira.
Code de cette leçon : src/province.controller.ts
1// Décorateurs Swagger sur le contrôleur des provinces
2import { Controller, Get, Post, Param, Body, Query } from '@nestjs/common';
3import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery } from '@nestjs/swagger';
4
5// TODO: Ajoute le décorateur @ApiTags('Provinciae')
6@Controller('provinciae')
7export class ProvinceController {
8
9 // TODO: Ajoute @ApiOperation avec summary et description
10 // TODO: Ajoute @ApiResponse pour le status 200
11 @Get()
12 findAll() {
13 return [
14 { name: 'Pannonia', governor: 'Lucius Verus' },
15 { name: 'Britannia', governor: 'Agricola' },
16 { name: 'Africa', governor: 'Septimius' },
17 ];
18 }
19
20 // TODO: Ajoute @ApiOperation avec summary
21 // TODO: Ajoute @ApiParam décrivant le paramètre 'id'
22 // TODO: Ajoute @ApiResponse pour 200 et 404
23 @Get(':id')
24 findOne(@Param('id') id: string) {
25 return { name: 'Pannonia', governor: 'Lucius Verus', legions: 3 };
26 }
27
28 // TODO: Ajoute @ApiOperation avec summary
29 // TODO: Ajoute @ApiQuery pour 'region' (optionnel)
30 // TODO: Ajoute @ApiQuery pour 'minLegions' (optionnel, type Number)
31 @Get('search')
32 search(
33 @Query('region') region?: string,
34 @Query('minLegions') minLegions?: number,
35 ) {
36 return [{ name: 'Pannonia', region: 'Europa', legions: 3 }];
37 }
38
39 // TODO: Ajoute @ApiOperation avec summary
40 // TODO: Ajoute @ApiResponse pour 201
41 @Post()
42 create(@Body() dto: any) {
43 return { id: '1', ...dto };
44 }
45}
46
47console.log("Décorateurs de contrôleur Swagger :");
48console.log("@ApiTags('name') - groups endpoints");
49console.log("@ApiOperation({ summary, description })");
50console.log("@ApiResponse({ status, description })");
51console.log("@ApiParam({ name, description, example })");
52console.log("@ApiQuery({ name, required, description })");
53Tu 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. Quelles sont les principales propriétés acceptées par le décorateur @ApiOperation() ?
2. Quand utilise-t-on le décorateur @ApiParam() dans un contrôleur NestJS ?
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
Ajoute @ApiQuery à l'endpoint de recherche et @ApiParam à l'endpoint avec le paramètre :id
- Tri vertical
Classe les décorateurs Swagger sur un endpoint, du décorateur de classe au corps de la méthode :
- Clique dans l'ordre
Ordonne les éléments du décorateur @ApiQuery dans le bon ordre :
- Éditeur de code
Ajoute @ApiTags, @ApiOperation, @ApiResponse, @ApiParam et @ApiQuery au contrôleur des provinces