# How to Implement API Versioning in NestJS: URI, Header, and Custom Strategies

> Learn to implement API versioning in NestJS using URI, header, and custom strategies. Master request routing for robust API management with this comprehensive guide.

- Repository: [nestjs/nest](https://github.com/nestjs/nest)
- Tags: how-to-guide
- Published: 2026-03-01

---

**NestJS provides built-in API versioning through `enableVersioning()` and the `@Version()` decorator, supporting URI paths, headers, media types, and custom extractors to route requests to version-specific handlers.**

NestJS offers a flexible, adapter-agnostic API versioning system that works with both Express and Fastify. According to the nestjs/nest source code, the framework implements this through metadata-driven route resolution and strategy-specific filtering. This guide demonstrates how to implement API versioning in NestJS using the four built-in versioning types defined in the core library.

## Understanding the Four Versioning Strategies

The `VersioningType` enum in [`packages/common/enums/version-type.enum.ts`](https://github.com/nestjs/nest/blob/main/packages/common/enums/version-type.enum.ts) defines four distinct strategies for supplying version information:

- **URI**: Version is part of the URL path (e.g., `/v1/users`)
- **HEADER**: Version is supplied via a custom request header (e.g., `X-API-Version: 1`)
- **MEDIA_TYPE**: Version is extracted from the `Accept` header parameter (e.g., `application/json;v=1`)
- **CUSTOM**: Version is determined by a user-defined extractor function

Each strategy uses different resolution mechanisms within the framework. URI-based versioning is handled by path manipulation in [`packages/core/router/route-path-factory.ts`](https://github.com/nestjs/nest/blob/main/packages/core/router/route-path-factory.ts), while the other three strategies rely on version filters injected by the HTTP adapters.

## Enabling Versioning in Your Application

To activate API versioning, call `enableVersioning()` on your application instance during bootstrap. This method is defined in [`packages/core/nest-application.ts`](https://github.com/nestjs/nest/blob/main/packages/core/nest-application.ts) and stores a `VersioningOptions` object in `ApplicationConfig` ([`packages/core/application-config.ts`](https://github.com/nestjs/nest/blob/main/packages/core/application-config.ts)).

```typescript
import { NestFactory } from '@nestjs/core';
import { VersioningType } from '@nestjs/common';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  app.enableVersioning({
    type: VersioningType.URI,
    prefix: 'api/v',       // Optional: results in /api/v1/...
    defaultVersion: '1',   // Fallback when no version is supplied
  });
  
  await app.listen(3000);
}
bootstrap();

```

The `VersioningOptions` interface ([`packages/common/interfaces/version-options.interface.ts`](https://github.com/nestjs/nest/blob/main/packages/common/interfaces/version-options.interface.ts)) accepts different parameters depending on the strategy type. For **HEADER** versioning, specify the header name. For **CUSTOM** versioning, provide an extractor function.

## Defining Versioned Controllers and Routes

Apply the `@Version()` decorator to individual route handlers to attach version metadata. The decorator writes to `VERSION_METADATA` using the reflection metadata API, as implemented in [`packages/common/decorators/core/version.decorator.ts`](https://github.com/nestjs/nest/blob/main/packages/common/decorators/core/version.decorator.ts).

```typescript
import { Controller, Get, Version } from '@nestjs/common';

@Controller('cats')
export class CatsController {
  @Get()
  findAll() {
    return 'All cats (any version)';
  }

  @Version('1')
  @Get('profile')
  getProfileV1() {
    return 'Profile v1';
  }

  @Version(['2', '3'])  // Multiple versions supported
  @Get('profile')
  getProfileV2orV3() {
    return 'Profile v2 or v3';
  }
}

```

Routes without the `@Version()` decorator are **version-neutral** (`VERSION_NEUTRAL`), meaning they respond to requests regardless of the version specified.

## How NestJS Resolves Versioned Requests

The framework resolves versioned routes differently depending on the strategy:

**URI Strategy**: The `RoutePathFactory` class in [`packages/core/router/route-path-factory.ts`](https://github.com/nestjs/nest/blob/main/packages/core/router/route-path-factory.ts) constructs the final URL path by prefixing the version string to the route. When you configure `prefix: 'api/v'` with version `'1'`, the factory generates `/api/v1/` prefixes.

**Header, Media Type, and Custom Strategies**: Each HTTP adapter implements a version filter that extracts the version from the incoming request and determines handler eligibility:

- **Express**: The `applyVersionFilter` method in [`packages/platform-express/adapters/express-adapter.ts`](https://github.com/nestjs/nest/blob/main/packages/platform-express/adapters/express-adapter.ts) performs the version matching logic.
- **Fastify**: The adapter uses `versionConstraint` and `deriveConstraint` functions in [`packages/platform-fastify/adapters/fastify-adapter.ts`](https://github.com/nestjs/nest/blob/main/packages/platform-fastify/adapters/fastify-adapter.ts) to leverage Fastify's native route constraints.

When a request lacks version information, the system falls back to the configured **default version**. If no default is set, the request matches only version-neutral routes.

## Complete Implementation Examples

### URI-Based Versioning

```typescript
app.enableVersioning({
  type: VersioningType.URI,
  prefix: 'v',
  defaultVersion: '1',
});

```

Test with curl:

```bash
curl http://localhost:3000/v1/cats/profile    # → "Profile v1"

curl http://localhost:3000/v2/cats/profile    # → "Profile v2 or v3"

curl http://localhost:3000/cats               # → "All cats (neutral route)"

```

### Header-Based Versioning

```typescript
app.enableVersioning({
  type: VersioningType.HEADER,
  header: 'X-API-Version',
});

```

The Express adapter checks the `X-API-Version` header against the `@Version()` metadata to route the request.

### Custom Extractor (Query Parameter)

```typescript
app.enableVersioning({
  type: VersioningType.CUSTOM,
  extractor: (request: Request) => request.query.v as string,
});

```

This delegates version extraction to your custom function, which the adapter invokes during request processing.

## Summary

- **Enable versioning globally** using `app.enableVersioning()` in your bootstrap function, which stores configuration in `ApplicationConfig`.
- **Choose from four strategies**: URI (path prefix), HEADER (custom header), MEDIA_TYPE (Accept header), or CUSTOM (extractor function).
- **Decorate routes** with `@Version()` to attach version metadata; omit it for version-neutral routes that match any request.
- **Route resolution** occurs in `RoutePathFactory` for URI strategy, or via adapter-specific filters in Express and Fastify adapters for other strategies.
- **Configure a default version** to handle requests that don't specify a version explicitly.

## Frequently Asked Questions

### How do I set a default version for unversioned requests?

Pass `defaultVersion` to `enableVersioning()`. When a request arrives without version information (no URI prefix, header, or extracted value), NestJS treats it as the default version. Without this configuration, unversioned requests match only version-neutral routes.

### Can a single route handler support multiple API versions?

Yes. The `@Version()` decorator accepts an array of strings: `@Version(['2', '3'])`. The metadata stored by [`packages/common/decorators/core/version.decorator.ts`](https://github.com/nestjs/nest/blob/main/packages/common/decorators/core/version.decorator.ts) supports multiple values, and the adapter's version filter checks for inclusion in that array.

### What happens if a request version doesn't match any route?

If the requested version (extracted via URI, header, or custom logic) doesn't match any registered `@Version()` metadata and no version-neutral route exists for that path, NestJS returns a 404 Not Found. The version filters in the Express and Fastify adapters explicitly reject non-matching requests before controller execution.

### Does API versioning work with Fastify?

Yes. The implementation in [`packages/platform-fastify/adapters/fastify-adapter.ts`](https://github.com/nestjs/nest/blob/main/packages/platform-fastify/adapters/fastify-adapter.ts) supports all four versioning types using Fastify's route constraint system. The `versionConstraint` and `deriveConstraint` functions handle version extraction and matching identically to the Express implementation, ensuring consistent behavior across HTTP adapters.