Skip to main content
nestrs supports three versioning strategies that map directly to the NestJS VersioningType options: URI prefix, custom request header, and Accept media-type negotiation. You pick one strategy per application and optionally annotate individual controllers or routes to restrict them to specific versions.

URI versioning

enable_uri_versioning nests all your routes under a version prefix. Requests to /v1/users are routed exactly like requests to /users in an unversioned app:
use nestrs::prelude::*;

#[module(controllers = [UsersController], providers = [UsersService])]
struct AppModule;

#[tokio::main]
async fn main() {
    NestFactory::create::<AppModule>()
        .enable_uri_versioning("v1")
        .listen(3000)
        .await;
}
With this setup a GET /v1/users request matches your UsersController handler for GET /users. The version prefix is applied globally — individual controllers can narrow to a specific version with #[version] (see below).

Header versioning

enable_header_versioning reads the version from a request header (default X-API-Version) and rewrites the internal path before routing:
NestFactory::create::<AppModule>()
    .enable_header_versioning("X-API-Version", Some("v1".to_string()))
    .listen(3000)
    .await;
The second argument is the default version to use when the header is absent. Pass None to treat unversioned requests as unversioned. You can also pass a full ApiVersioningPolicy through enable_api_versioning for more control:
use nestrs::prelude::*;

NestFactory::create::<AppModule>()
    .enable_api_versioning(ApiVersioningPolicy {
        kind: VersioningType::Header,
        header_name: Some("X-API-Version".to_string()),
        default_version: Some("v1".to_string()),
    })
    .listen(3000)
    .await;

Media-type versioning

enable_media_type_versioning parses the version from the Accept header using the version= parameter, for example Accept: application/vnd.api+json;version=2:
NestFactory::create::<AppModule>()
    .enable_media_type_versioning(Some("v1".to_string()))
    .listen(3000)
    .await;

Per-controller versioning with #[version]

Annotate a controller struct with #[version("v2")] to bind all its routes to that version. When URI versioning is active, only /v2/... requests are matched. When header or media-type versioning is active, only requests carrying that version are matched:
use nestrs::prelude::*;

#[controller(prefix = "/api")]
pub struct AppController;

#[version("v2")]
#[controller(prefix = "/api")]
pub struct AppControllerV2;

#[routes(state = AppService)]
impl AppController {
    #[get("/")]
    pub async fn root() -> &'static str {
        "Hello World v1"
    }
}

#[routes(state = AppService)]
impl AppControllerV2 {
    #[get("/")]
    pub async fn root() -> &'static str {
        "Hello World v2"
    }
}

Per-route versioning with #[ver]

Use #[ver("v2")] on a single handler to make that route available only for the given version, while the rest of the controller remains at the default version:
#[routes(state = AppService)]
impl AppController {
    #[get("/")]
    pub async fn root() -> &'static str {
        "Hello World"
    }

    #[get("/feature")]
    #[ver("v2")]
    pub async fn versioned_feature() -> &'static str {
        "feature-route-v2"
    }
}

Reading the resolved version in a handler

The NestApiVersion extractor gives you the resolved version string for the current request. Use it in guards, interceptors, or handler logic:
use nestrs::prelude::*;

#[routes(state = AppState)]
impl DiagnosticsController {
    #[get("/version")]
    pub async fn which_version(
        version: Option<axum::Extension<NestApiVersion>>,
    ) -> String {
        version
            .map(|v| v.0 .0.clone())
            .unwrap_or_else(|| "unversioned".to_string())
    }
}
NestApiVersion is inserted into request extensions by the versioning middleware for header and media-type strategies. For URI versioning the version is part of the path and is not separately injected.

Strategy comparison

StrategyHow version is carriedClient change required
enable_uri_versioning("v1")URL prefix: /v1/usersUpdate base URL
enable_header_versioning("X-API-Version", ...)Request headerAdd header
enable_media_type_versioning(...)Accept header param: version=2Modify Accept
URI versioning is the most cache-friendly strategy since each version has a distinct URL. Header and media-type versioning keep URLs stable but require clients to set the right header on every request.