Cours NestJS · Module 11: Swagger et OpenAPI

Projet principal : Documentation complète de l'API Imperium

3 min de lecture
Dans cette leçon3

Le moment est venu pour le grand œuvre - créer les Chroniques complètes de l'Empire ! Dans ce projet, tu combineras toutes les connaissances de ce module pour créer une API entièrement documentée pour la gestion de l'Empire romain.

Exigences du projet

Tes Chroniques de l'Empire doivent inclure :

1. Configuration de Swagger dans main.ts

1import { NestFactory } from '@nestjs/core';
2import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
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({ whitelist: true, transform: true }));
10
11  const config = new DocumentBuilder()
12    .setTitle('Imperium Romanum API')
13    .setDescription('Complete API documentation for managing the Roman Empire')
14    .setVersion('1.0')
15    .addTag('Legiones', 'Managing legions and soldiers')
16    .addTag('Provinciae', 'Managing provinces of the Empire')
17    .addTag('Tributum', 'Tax system and treasury')
18    .addBearerAuth(
19      { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' },
20      'JWT-auth',
21    )
22    .setContact('Consul Maximus', 'https://imperium.rome', 'consul@rome.gov')
23    .setLicense('Lex Romana', 'https://imperium.rome/lex')
24    .build();
25
26  const document = SwaggerModule.createDocument(app, config);
27
28  SwaggerModule.setup('api/docs', app, document, {
29    customSiteTitle: 'Chronicles of the Imperium API',
30    swaggerOptions: {
31      persistAuthorization: true,
32      filter: true,
33      showRequestDuration: true,
34    },
35  });
36
37  await app.listen(3000);
38}
39bootstrap();

2. DTO avec documentation complète

1import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
2
3export enum LegionaryRank {
4  MILES = 'Miles',
5  OPTIO = 'Optio',
6  CENTURIO = 'Centurio',
7  TRIBUNUS = 'Tribunus',
8  LEGATUS = 'Legatus',
9}
10
11export class CreateLegionaryDto {
12  @ApiProperty({ description: 'Name of the legionary', example: 'Marcus Aurelius', minLength: 2 })
13  name: string;
14
15  @ApiProperty({ description: 'Rank', enum: LegionaryRank, example: LegionaryRank.MILES })
16  rank: LegionaryRank;
17
18  @ApiProperty({ description: 'Assigned legion', example: 'Legio X Gemina' })
19  legio: string;
20
21  @ApiPropertyOptional({ description: 'Years of experience', example: 3, minimum: 0, maximum: 40 })
22  experience?: number;
23}
24
25export class LegionaryResponseDto {
26  @ApiProperty({ description: 'Legionary ID', example: '64a1b2c3d4e5f6a7b8c9d0e1' })
27  id: string;
28
29  @ApiProperty({ description: 'Name', example: 'Marcus Aurelius' })
30  name: string;
31
32  @ApiProperty({ description: 'Rank', enum: LegionaryRank })
33  rank: LegionaryRank;
34
35  @ApiProperty({ description: 'Legion', example: 'Legio X Gemina' })
36  legio: string;
37
38  @ApiProperty({ description: 'Experience', example: 5 })
39  experience: number;
40
41  @ApiProperty({ description: 'Recruitment date' })
42  recruitedAt: Date;
43}

3. Contrôleur avec documentation complète

1import { Controller, Get, Post, Put, Delete, Param, Body, Query, UseGuards } from '@nestjs/common';
2import { ApiTags, ApiOperation, ApiParam, ApiQuery, ApiBearerAuth,
3  ApiOkResponse, ApiCreatedResponse, ApiNotFoundResponse,
4  ApiBadRequestResponse, ApiUnauthorizedResponse } from '@nestjs/swagger';
5
6@ApiTags('Legiones')
7@Controller('legiones')
8export class LegionController {
9  @ApiOperation({ summary: 'Get all legions' })
10  @ApiOkResponse({ description: 'List of legions', type: [LegionaryResponseDto] })
11  @ApiQuery({ name: 'rank', required: false, enum: LegionaryRank })
12  @Get()
13  findAll(@Query('rank') rank?: LegionaryRank) {}
14
15  @ApiOperation({ summary: 'Get legionary by ID' })
16  @ApiParam({ name: 'id', description: 'Legionary ID' })
17  @ApiOkResponse({ type: LegionaryResponseDto })
18  @ApiNotFoundResponse({ description: 'Legionary not found' })
19  @Get(':id')
20  findOne(@Param('id') id: string) {}
21
22  @ApiBearerAuth('JWT-auth')
23  @ApiOperation({ summary: 'Recruit a legionary' })
24  @ApiCreatedResponse({ type: LegionaryResponseDto })
25  @ApiBadRequestResponse({ description: 'Invalid data' })
26  @ApiUnauthorizedResponse({ description: 'Unauthorized' })
27  @Post()
28  create(@Body() dto: CreateLegionaryDto) {}
29}

Résumé du module

Dans ce module, tu as appris :

  1. Ce qu'est OpenAPI/Swagger - le standard de documentation d'API
  2. Installation et configuration - DocumentBuilder et SwaggerModule
  3. Décorateurs de contrôleur - @ApiTags, @ApiOperation, @ApiResponse, @ApiParam, @ApiQuery
  4. Documentation des DTO - @ApiProperty avec descriptions, exemples et validation
  5. Réponses avancées - décorateurs dédiés pour chaque code de statut
  6. Personnalisation - personnaliser Swagger UI
  7. CLI Plugin - génération automatique de la documentation
  8. Versionnage - plusieurs docs pour différentes versions de l'API

Maintenant tes Chroniques de l'Empire sont complètes ! Chaque endpoint est documenté comme un décret impérial, et Swagger UI sert de Forum des Annales pour tous les développeurs.

Exercice pratique

Crée la documentation complète de l'API pour Imperium Romanum :

Code de cette leçon : src/app.controller.ts
1// Projet principal : documentation complète de l'API Imperium
2import { Controller, Get, Post, Put, Delete, Param, Body, Query } from '@nestjs/common';
3import {
4  ApiTags, ApiOperation, ApiParam, ApiQuery, ApiBearerAuth,
5  ApiOkResponse, ApiCreatedResponse, ApiNotFoundResponse,
6  ApiBadRequestResponse, ApiUnauthorizedResponse,
7} from '@nestjs/swagger';
8import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
9
10// ===== DTO =====
11
12enum LegionaryRank {
13  MILES = 'Miles',
14  OPTIO = 'Optio',
15  CENTURIO = 'Centurio',
16  TRIBUNUS = 'Tribunus',
17  LEGATUS = 'Legatus',
18}
19
20class CreateLegionaryDto {
21  @ApiProperty({ description: 'Nom du légionnaire', example: 'Marcus Aurelius' })
22  name: string;
23
24  @ApiProperty({ description: 'Rang', enum: LegionaryRank, example: LegionaryRank.MILES })
25  rank: LegionaryRank;
26
27  @ApiProperty({ description: 'Assigned legion', example: 'Legio X Gemina' })
28  legio: string;
29
30  @ApiPropertyOptional({ description: 'Expérience', example: 3, minimum: 0 })
31  experience?: number;
32}
33
34class LegionaryResponseDto {
35  @ApiProperty({ example: '64a1b2c3d4e5f6a7b8c9d0e1' })
36  id: string;
37
38  @ApiProperty({ example: 'Marcus Aurelius' })
39  name: string;
40
41  @ApiProperty({ enum: LegionaryRank })
42  rank: LegionaryRank;
43
44  @ApiProperty({ example: 'Legio X Gemina' })
45  legio: string;
46
47  @ApiProperty({ example: 5 })
48  experience: number;
49}
50
51// ===== CONTROLLER =====
52
53@ApiTags('Legiones')
54@Controller('legiones')
55class LegionController {
56  @ApiOperation({ summary: 'Get all legions' })
57  @ApiOkResponse({ description: 'Liste des légions', type: [LegionaryResponseDto] })
58  @ApiQuery({ name: 'rank', required: false, enum: LegionaryRank })
59  @Get()
60  findAll(@Query('rank') rank?: LegionaryRank) {
61    return [
62      { id: '1', name: 'Marcus', rank: 'Centurio', legio: 'Legio X', experience: 10 },
63    ];
64  }
65
66  @ApiOperation({ summary: 'Get le légionnaire par ID' })
67  @ApiParam({ name: 'id', description: 'ID du légionnaire' })
68  @ApiOkResponse({ type: LegionaryResponseDto })
69  @ApiNotFoundResponse({ description: 'Legionista not found' })
70  @Get(':id')
71  findOne(@Param('id') id: string) {
72    return { id, name: 'Marcus', rank: 'Centurio', legio: 'Legio X', experience: 10 };
73  }
74
75  @ApiBearerAuth('JWT-auth')
76  @ApiOperation({ summary: 'Recrute un légionnaire' })
77  @ApiCreatedResponse({ type: LegionaryResponseDto })
78  @ApiBadRequestResponse({ description: 'Invalid data' })
79  @ApiUnauthorizedResponse({ description: 'Non autorisé' })
80  @Post()
81  create(@Body() dto: CreateLegionaryDto) {
82    return { id: '2', ...dto, experience: dto.experience || 0 };
83  }
84}
85
86console.log("Documentation complète de l'Imperium API");
87console.log("DTO: CreateLegionaryDto, LegionaryResponseDto");
88console.log("Contrôleur : LegionController avec documentation complète");
89console.log("Décorateurs : Tags, Operation, Param, Query, Response, Auth");
90

Tu 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. 1. Quels décorateurs Swagger un endpoint GET /:id bien documenté devrait-il avoir ?

  2. 2. Quelle est la meilleure façon de sécuriser l'accès à Swagger UI dans un environnement de production ?

Voici 2 des 4 questions de cette leçon. Réponds aux autres dans le jeu.

Tâches pratiques dans le jeu

  • Tri vertical

    Classe les éléments d'une documentation d'API complète, de la configuration à la présentation :

  • Éditeur de code

    Crée CreateArmyDto avec l'enum ArmyUnit, le CommanderDto imbriqué, un tableau provinces et la validation soldiers

  • Clique dans l'ordre

    Ordonne les éléments d'un champ de DTO documenté, du décorateur au type :

  • Tri horizontal

    Ordonne les arguments de SwaggerModule.setup() dans le bon ordre :

  • Tri vertical

    Classe les stratégies de versionnage, de la plus simple à la plus avancée :

  • Clique dans l'ordre

    Ordonne les éléments de l'import des décorateurs Swagger dans le bon ordre :

  • Tri vertical

    Classe les étapes de création de la documentation Swagger, de l'installation à l'utilisation :

  • Tri horizontal

    Ordonne les éléments du décorateur @ApiResponse dans le bon ordre :