When building APIs, managing different versions is critical for backward compatibility and feature updates. NestJS provides built-in support for API versioning, allowing developers to handle multiple versions of APIs gracefully.
This article explores API versioning in NestJS, its types, and how to implement them with examples.
Why API Versioning?
API versioning helps manage changes in your application without breaking functionality for existing clients. It ensures:
- Smooth transitions between versions.
- Backward compatibility for older API clients.
- Clear separation of new features or deprecations.
Enabling Versioning in NestJS
NestJS allows versioning to be enabled globally using the enableVersioning()
method. The versioning strategies available are:
- URI Versioning
- Header Versioning
- Media Type Versioning
- Custom Versioning
1. URI Versioning
In URI versioning, the version is part of the API path, typically prefixed with v
(e.g., /v1/resource
).
Enable URI Versioning
import { VersioningType } from "@nestjs/common";
app.enableVersioning({
type: VersioningType.URI, // Version is specified in the URL.
defaultVersion: "1", // Sets a default version.
});
Defining Versioned Routes
import { Controller, Get, Version } from "@nestjs/common";
@Controller("users")
export class UsersController {
@Get()
@Version("1") // Handler for version 1
getUsersV1() {
return { message: "This is version 1 of the users API" };
}
@Get()
@Version("2") // Handler for version 2
getUsersV2() {
return { message: "This is version 2 of the users API" };
}
}
Requests
- GET /v1/users →
{ message: 'This is version 1 of the users API' }
- GET /v2/users →
{ message: 'This is version 2 of the users API' }
2. Header Versioning
In header versioning, the version is specified in a custom HTTP header, such as X-API-Version
.
Enable Header Versioning
app.enableVersioning({
type: VersioningType.HEADER, // Version is specified in headers.
header: "X-API-Version", // Custom header name.
});
Defining Versioned Routes
@Controller("users")
export class UsersController {
@Get()
@Version("1") // Handler for version 1
getUsersV1() {
return { message: "This is version 1 of the users API" };
}
@Get()
@Version("2") // Handler for version 2
getUsersV2() {
return { message: "This is version 2 of the users API" };
}
}
Requests
- GET /users with header
X-API-Version: 1
→{ message: 'This is version 1 of the users API' }
- GET /users with header
X-API-Version: 2
→{ message: 'This is version 2 of the users API' }
3. Media Type Versioning
In media type versioning, the API version is included in the Accept
header, such as application/vnd.myapp.v1+json
.
Enable Media Type Versioning
app.enableVersioning({
type: VersioningType.MEDIA_TYPE, // Version is in Accept header.
});
Defining Versioned Routes
@Controller("users")
export class UsersController {
@Get()
@Version("1") // Handler for version 1
getUsersV1() {
return { message: "This is version 1 of the users API" };
}
@Get()
@Version("2") // Handler for version 2
getUsersV2() {
return { message: "This is version 2 of the users API" };
}
}
Requests
- GET /users with header
Accept: application/vnd.myapp.v1+json
→{ message: 'This is version 1 of the users API' }
- GET /users with header
Accept: application/vnd.myapp.v2+json
→{ message: 'This is version 2 of the users API' }
4. Custom Versioning
NestJS also supports custom strategies for versioning. You can define your own logic by implementing the VersioningOptions
.
Custom Versioning Example
import { VersioningOptions, VersioningType } from "@nestjs/common";
app.enableVersioning({
type: VersioningType.CUSTOM, // Use custom versioning logic.
extractor: (request) => {
// Extract version from a query parameter, cookie, etc.
return request.query["version"] || "1";
},
});
Defining Versioned Routes
@Controller("users")
export class UsersController {
@Get()
@Version("1") // Handler for version 1
getUsersV1() {
return { message: "This is version 1 of the users API" };
}
@Get()
@Version("2") // Handler for version 2
getUsersV2() {
return { message: "This is version 2 of the users API" };
}
}
Requests
- GET /users?version=1 →
{ message: 'This is version 1 of the users API' }
- GET /users?version=2 →
{ message: 'This is version 2 of the users API' }
Default Version
If no version is provided in the request, the defaultVersion
option specifies which version to use. This ensures backward compatibility for older clients.
Example:
app.enableVersioning({
type: VersioningType.URI,
defaultVersion: "1", // Default to version 1
});
Request to /users
(without version) will be mapped to /v1/users
.
Combining Versioning Types
NestJS allows you to combine multiple versioning strategies for more flexibility. For example, you can use both URI and Header versioning.
Example:
app.enableVersioning({
type: [VersioningType.URI, VersioningType.HEADER],
});
Conclusion
API versioning in NestJS provides a powerful and flexible mechanism to manage multiple API versions. With strategies like URI, Header, Media Type, and Custom versioning, you can choose the approach that best suits your application’s requirements. By enabling versioning, you ensure backward compatibility and scalability as your application evolves.
By following this guide, you can implement robust versioning in your NestJS application to handle changes and updates efficiently.