As an API evolves, breaking changes are sometimes unavoidable. This lesson covers NestJS's built-in versioning support so you can introduce breaking changes without disrupting existing clients.
Enabling Versioning
NestJS supports four versioning strategies out of the box: URI-based (/v1/cats), header-based, media-type-based, and custom. Versioning is enabled once at the application level, then applied per-controller or per-route.
With URI versioning enabled, a controller version of '2' automatically becomes reachable at /v2/..., while unspecified controllers default to /v1/....
Versioning a Controller or Route
@Controller({ path: 'cats', version: '2' })
export class CatsControllerV2 {}
@Controller('cats')
export class CatsController {
@Version('2')
@Get()
findAllV2() {}
}
version can be set on the whole controller or on individual routes.
VERSION_NEUTRAL marks a route as available under every version.
URI versioning is the most explicit and cache-friendly strategy for public APIs.
Header and media-type versioning keep URLs stable but require documenting a custom header or Accept value.
Versioning Strategy Cheatsheet
The four versioning types NestJS supports out of the box.
Type
How a Version Is Selected
URI
A version segment in the path, e.g. /v2/cats
HEADER
A custom request header, e.g. X-API-Version: 2
MEDIA_TYPE
An Accept header parameter, e.g. application/json;v=2
CUSTOM
A custom extractor function you provide
When You Actually Need Versioning
Not every change requires a new version. Additive, backward-compatible changes (new optional fields, new endpoints) don't need one. Reserve versioning for genuinely breaking changes: removing a field, changing a field's type, or altering response structure that existing clients depend on.
Adding a new optional response field: no version bump needed.
Renaming or removing an existing field: needs a new version.
Changing an endpoint's URL structure entirely: needs a new version.
Fixing an actual bug in existing behavior: usually not a version bump, it's a bug fix.
Maintaining Multiple Versions Cleanly
Rather than branching logic with conditionals inside one controller, keep separate versioned controllers that share a common service layer, only the request/response mapping differs between versions, not the underlying business logic.
Common Mistakes
Introducing a new API version for a purely additive, non-breaking change.
Duplicating business logic across versioned controllers instead of sharing one service layer.
Never deprecating or removing old versions, leaving an ever-growing surface to maintain.
Choosing header-based versioning for a public API without documenting the required header clearly.
Key Takeaways
NestJS supports URI, header, media-type, and custom versioning strategies.
Versioning can be applied at the controller level or the individual route level.
Reserve new versions for genuinely breaking changes, not additive ones.
Share a common service layer across versioned controllers to avoid duplicated logic.
Pro Tip
Publish a clear deprecation policy alongside your versioning strategy (e.g. "v1 is supported for 6 months after v2 ships"), a versioning scheme without a deprecation timeline just accumulates permanent maintenance burden.
You now know how to version a NestJS API safely. Next, move into Middleware to run logic before requests reach your route handlers.