NestJS course Β· Module 11: Swagger and OpenAPI

Main Project: Complete Imperium API Documentation

3 min read
In this lesson3

The time has come for the grand opus - creating the complete Chronicles of the Empire! In this project, you will combine all the knowledge from this module to create a fully documented API for managing the Roman Empire.

Project Requirements

Your Chronicles of the Empire must include:

1. Swagger Configuration in 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. DTOs with Full Documentation

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. Controller with Full Documentation

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}

Module Summary

In this module, you learned:

  1. What OpenAPI/Swagger is - the API documentation standard
  2. Installation and configuration - DocumentBuilder and SwaggerModule
  3. Controller decorators - @ApiTags, @ApiOperation, @ApiResponse, @ApiParam, @ApiQuery
  4. DTO documentation - @ApiProperty with descriptions, examples, and validation
  5. Advanced responses - dedicated decorators for each status code
  6. Customization - personalizing Swagger UI
  7. CLI Plugin - automatic documentation generation
  8. Versioning - multiple docs for different API versions

Now your Chronicles of the Empire are complete! Every endpoint is documented like an imperial decree, and Swagger UI serves as the Forum of Annals for all developers.

Practical Exercise

Create the complete API documentation for Imperium Romanum:

Code for this lesson: src/app.controller.ts
1// Main project: Complete Imperium API documentation
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: 'Legionary name', example: 'Marcus Aurelius' })
22  name: string;
23
24  @ApiProperty({ description: 'Rank', 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: 'Experience', 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: 'List of legions', 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 a legionary by ID' })
67  @ApiParam({ name: 'id', description: 'Legionary ID' })
68  @ApiOkResponse({ type: LegionaryResponseDto })
69  @ApiNotFoundResponse({ description: 'Legionary 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: 'Recruit a legionary' })
77  @ApiCreatedResponse({ type: LegionaryResponseDto })
78  @ApiBadRequestResponse({ description: 'Invalid data' })
79  @ApiUnauthorizedResponse({ description: 'Unauthorized' })
80  @Post()
81  create(@Body() dto: CreateLegionaryDto) {
82    return { id: '2', ...dto, experience: dto.experience || 0 };
83  }
84}
85
86console.log("Complete Imperium API documentation");
87console.log("DTO: CreateLegionaryDto, LegionaryResponseDto");
88console.log("Controller: LegionController with full documentation");
89console.log("Decorators: Tags, Operation, Param, Query, Response, Auth");
90

Spotted a mistake in this lesson?

Check yourself

Answer the questions from this lesson. Pick an answer to see right away whether it is correct.

  1. 1. Which Swagger decorators should a well-documented GET /:id endpoint have?

  2. 2. What is the best way to secure access to Swagger UI in a production environment?

These are 2 of 4 questions for this lesson. Solve the rest in the game.

Hands-on tasks in the game

  • Vertical ordering

    Order the elements of complete API documentation from configuration to presentation:

  • Code editor

    Create CreateArmyDto with enum ArmyUnit, nested CommanderDto, a provinces array, and soldiers validation

  • Click in order

    Arrange the elements of a documented DTO field from decorator to type:

  • Horizontal ordering

    Arrange the SwaggerModule.setup() arguments in the correct order:

  • Vertical ordering

    Order the versioning strategies from the simplest to the most advanced:

  • Click in order

    Arrange the elements of the Swagger decorators import in the correct order:

  • Vertical ordering

    Order the stages of creating Swagger documentation from installation to usage:

  • Horizontal ordering

    Arrange the elements of the @ApiResponse decorator in the correct order:

Useful articles