Introduction
When your API has multiple versions, documentation should reflect each version's specifics. Swagger can generate separate documentation for each version, helping developers understand what's available in each.
Key Concepts
- Multi-Document Setup: Separate Swagger docs per version
- Version Tags: Group endpoints by version
- Deprecation Markers: Show deprecated endpoints
- Version Comparison: Help clients see differences
Real World Context
Versioned documentation helps:
- Developers using specific versions
- Migration planning
- API exploration
- Support team troubleshooting
Deep Dive
Multiple Swagger Documents
Generate separate Swagger documents per major version by filtering modules with the include option.
typescriptasync function bootstrap() { const app = await NestFactory.create(AppModule); app.enableVersioning({ type: VersioningType.URI, }); // V1 Documentation const v1Config = new DocumentBuilder() .setTitle('API v1') .setDescription('Version 1 of the API (Deprecated)') .setVersion('1.0') .addBearerAuth() .build(); const v1Document = SwaggerModule.createDocument(app, v1Config, { include: [UsersV1Module, ProductsV1Module], }); SwaggerModule.setup('api/v1/docs', app, v1Document); // V2 Documentation const v2Config = new DocumentBuilder() .setTitle('API v2') .setDescription('Version 2 of the API (Current)') .setVersion('2.0') .addBearerAuth() .build(); const v2Document = SwaggerModule.createDocument(app, v2Config, { include: [UsersV2Module, ProductsV2Module], }); SwaggerModule.setup('api/v2/docs', app, v2Document); await app.listen(3000); }
Each document is mounted at a different URL path (/api/v1/docs vs /api/v2/docs) for clear separation.
Version Tags in Single Document
If you prefer a single Swagger document, use version-prefixed tags to group endpoints visually.
typescriptconst config = new DocumentBuilder() .setTitle('API') .addTag('v1-users', 'Users API v1 (deprecated)') .addTag('v2-users', 'Users API v2') .build(); @Controller({ path: 'users', version: '1' }) @ApiTags('v1-users') export class UsersV1Controller { ... } @Controller({ path: 'users', version: '2' }) @ApiTags('v2-users') export class UsersV2Controller { ... }
Tag descriptions appear in the Swagger UI sidebar, making it easy to identify deprecated groups.
Deprecation in Swagger
Set deprecated: true in @ApiOperation() to render the endpoint with a strikethrough in Swagger UI.
typescript@Controller('users') export class UsersController { @Get() @Version('1') @ApiOperation({ summary: 'Get all users', deprecated: true, description: 'DEPRECATED: Use /v2/users instead. Will be removed on 2025-12-31.', }) findAllV1() { ... } @Get() @Version('2') @ApiOperation({ summary: 'Get all users with pagination' }) findAllV2() { ... } }
The description field is a good place to include the sunset date and a direct link to the replacement endpoint.
Schema Versioning
Register both DTO versions as extra models and reference the correct one per versioned endpoint.
typescript// Register both DTO versions const document = SwaggerModule.createDocument(app, config, { extraModels: [UserV1Dto, UserV2Dto], }); // Reference specific version @Get() @Version('1') @ApiOkResponse({ type: UserV1Dto }) findAllV1() { ... } @Get() @Version('2') @ApiOkResponse({ type: UserV2Dto }) findAllV2() { ... }
This ensures each version's documentation shows the exact response shape clients should expect.
Documentation Landing Page
Provide a root endpoint that lists all available API versions, their status, and documentation URLs.
typescript@Controller() export class ApiDocsController { @Get() @ApiExcludeEndpoint() redirectToDocs(@Res() res: Response) { res.redirect('/api/v2/docs'); } } // Or create a landing page @Controller('api') export class ApiVersionsController { @Get() getVersions() { return { versions: [ { version: 'v1', status: 'deprecated', docsUrl: '/api/v1/docs', sunsetDate: '2025-12-31', }, { version: 'v2', status: 'current', docsUrl: '/api/v2/docs', }, ], latest: 'v2', }; } }
The landing page doubles as a discovery endpoint for automated tools that need to find the latest API version.
Version-Specific Examples
Add example values to @ApiProperty() that reflect each version's actual response format.
typescriptexport class UserV1Dto { @ApiProperty({ example: '123' }) id: string; @ApiProperty({ example: 'John Doe' }) name: string; } export class UserV2Dto { @ApiProperty({ example: '123' }) id: string; @ApiProperty({ example: 'John' }) firstName: string; @ApiProperty({ example: 'Doe' }) lastName: string; }
Version-specific examples make the migration path obvious when developers compare v1 and v2 side by side.
Common Pitfalls
- Single doc for all versions: Confusing when schemas differ.
- No deprecation markers: Users don't know what's outdated.
- Missing examples: Version differences unclear without them.
Best Practices
- Create separate Swagger documents per major version
- Mark deprecated endpoints clearly
- Include examples showing version differences
- Provide a landing page listing all versions
- Link to migration guides from deprecated endpoints
Summary
Versioned Swagger documentation uses multiple documents or tags for different versions. Mark deprecated endpoints, include version-specific examples, and provide a landing page listing available versions with their status.
Code Examples
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableVersioning({
type: VersioningType.URI,
});
// V1 Documentation
const v1Config = new DocumentBuilder()
.setTitle('API v1')
.setDescription('Version 1 of the API (Deprecated)')
.setVersion('1.0')
.addBearerAuth()
.build();
const v1Document = SwaggerModule.createDocument(app, v1Config, {
include: [UsersV1Module, ProductsV1Module],
});
SwaggerModule.setup('api/v1/docs', app, v1Document);
// V2 Documentation
const v2Config = new DocumentBuilder()
.setTitle('API v2')
.setDescription('Version 2 of the API (Current)')
.setVersion('2.0')
.addBearerAuth()
.build();
const v2Document = SwaggerModule.createDocument(app, v2Config, {
include: [UsersV2Module, ProductsV2Module],
});
SwaggerModule.setup('api/v2/docs', app, v2Document);
await app.listen(3000);
}