We use cookies to enhance your experience on the site
CodeWorlds

API Versioning - Eras of the Empire

Just as the history of the Roman Empire was divided into eras - Kingdom, Republic, Empire - our API can have multiple versions. Versioning allows introducing changes without breaking existing integrations.

Versioning in NestJS

NestJS supports three versioning strategies:

  1. URI Versioning - version in the URL path:
    /v1/legiones
  2. Header Versioning - version in the HTTP header
  3. Media Type Versioning - version in Content-Type

URI Versioning (most commonly used)

1// main.ts
2import { VersioningType } from '@nestjs/common';
3
4async function bootstrap() {
5  const app = await NestFactory.create(AppModule);
6
7  app.enableVersioning({
8    type: VersioningType.URI,
9    defaultVersion: '1',
10  });
11
12  await app.listen(3000);
13}

Controller Versioning

After enabling versioning, controllers can declare their version:

1@Controller({ path: 'legiones', version: '1' })
2export class LegionV1Controller {
3  @Get()
4  findAll() {
5    return [{ name: 'Legio X', soldiers: 5000 }];
6  }
7}
8
9@Controller({ path: 'legiones', version: '2' })
10export class LegionV2Controller {
11  @Get()
12  findAll() {
13    return [{
14      name: 'Legio X Gemina',
15      soldiers: 5200,
16      province: 'Pannonia',
17      commander: 'Marcus Aurelius',
18    }];
19  }
20}

Versioning Individual Endpoints

1@Controller('legiones')
2export class LegionController {
3  @Version('1')
4  @Get()
5  findAllV1() {
6    return this.legionService.findAllSimple();
7  }
8
9  @Version('2')
10  @Get()
11  findAllV2() {
12    return this.legionService.findAllDetailed();
13  }
14}

Swagger with Multiple API Versions

When the API has multiple versions, it is worth creating separate Swagger documents for each version:

1async function bootstrap() {
2  const app = await NestFactory.create(AppModule);
3
4  app.enableVersioning({
5    type: VersioningType.URI,
6    defaultVersion: '1',
7  });
8
9  // Document for version 1
10  const configV1 = new DocumentBuilder()
11    .setTitle('Imperium API v1')
12    .setDescription('Republic Era - basic endpoints')
13    .setVersion('1.0')
14    .addBearerAuth()
15    .build();
16
17  const documentV1 = SwaggerModule.createDocument(app, configV1, {
18    include: [LegionV1Module, ProvinceV1Module],
19  });
20  SwaggerModule.setup('api/v1/docs', app, documentV1);
21
22  // Document for version 2
23  const configV2 = new DocumentBuilder()
24    .setTitle('Imperium API v2')
25    .setDescription('Empire Era - extended endpoints')
26    .setVersion('2.0')
27    .addBearerAuth()
28    .build();
29
30  const documentV2 = SwaggerModule.createDocument(app, configV2, {
31    include: [LegionV2Module, ProvinceV2Module],
32  });
33  SwaggerModule.setup('api/v2/docs', app, documentV2);
34
35  await app.listen(3000);
36}

Grouping Versions with Tags

Alternatively, you can use tags to differentiate versions within a single document:

1@ApiTags('Legiones v1')
2@Controller({ path: 'legiones', version: '1' })
3export class LegionV1Controller {
4  @ApiOperation({ summary: '[v1] Get legions' })
5  @Get()
6  findAll() {
7    return this.service.findAllV1();
8  }
9}
10
11@ApiTags('Legiones v2')
12@Controller({ path: 'legiones', version: '2' })
13export class LegionV2Controller {
14  @ApiOperation({ summary: '[v2] Get legions with details' })
15  @Get()
16  findAll() {
17    return this.service.findAllV2();
18  }
19}

Decorating Versions in @ApiOperation

A good practice is to mark versions in endpoint descriptions:

1@ApiOperation({
2  summary: 'Get list of legions',
3  description: `
4    **Version 2.0** - Extended response with location and commander.
5    Replaces the v1 endpoint which returned only basic data.
6    Version v1 will be deprecated in Q3 2025.
7  `,
8  deprecated: false,
9})
10@Get()
11findAll() {}

Marking Endpoints as Deprecated

When an old version of an endpoint is being retired:

1@ApiOperation({
2  summary: '[DEPRECATED] Get legions',
3  description: 'Use /v2/legiones instead of this endpoint',
4  deprecated: true,
5})
6@Get()
7findAllV1() {
8  return this.service.findAllV1();
9}

Practical Exercise

Configure API versioning with two Swagger documents:

Go to CodeWorlds