typescript 47 lines · 9 steps

How API versioning works in NestJS

Two controllers share the /users path but serve different logic per API version, while a neutral controller opts out entirely.

Explained by highlit
1import { Controller, Get, Param, Post, Body, Version, VERSION_NEUTRAL } from '@nestjs/common';
2import { UsersService } from './users.service';
3import { CreateUserDto } from './dto/create-user.dto';
4 
5@Controller({ path: 'users', version: '1' })
6export class UsersV1Controller {
7 constructor(private readonly usersService: UsersService) {}
8 
9 @Get()
10 findAll() {
11 return this.usersService.findAll();
12 }
13 
14 @Get(':id')
15 findOne(@Param('id') id: string) {
16 return this.usersService.findOne(id);
17 }
18}
19 
20@Controller({ path: 'users', version: '2' })
21export class UsersV2Controller {
22 constructor(private readonly usersService: UsersService) {}
23 
24 @Get()
25 findAll() {
26 return this.usersService.findAllPaginated();
27 }
28 
29 @Version(['2', '3'])
30 @Get(':id')
31 findOne(@Param('id') id: string) {
32 return this.usersService.findOneWithProfile(id);
33 }
34 
35 @Post()
36 create(@Body() dto: CreateUserDto) {
37 return this.usersService.create(dto);
38 }
39}
40 
41@Controller({ path: 'health', version: VERSION_NEUTRAL })
42export class HealthController {
43 @Get()
44 check() {
45 return { status: 'ok' };
46 }
47}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Version metadata lets two controllers claim the same path without colliding, so each version evolves independently.
  2. 2Per-route @Version overrides let a single handler answer for multiple API versions when behavior hasn't changed.
  3. 3VERSION_NEUTRAL exempts a route from versioning entirely, ideal for cross-cutting endpoints like health checks.

Related explainers

typescript
import { registerLocaleData } from '@angular/common';
import localeFr from '@angular/common/locales/fr';
import localeFrExtra from '@angular/common/locales/extra/fr';
import localeDe from '@angular/common/locales/de';

Locale-aware bootstrapping in Angular

i18n localization dependency-injection
Intermediate 8 steps
typescript
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import * as Joi from 'joi';
 

Validating env config at boot in NestJS

configuration schema-validation environment-variables
Intermediate 8 steps
java
@Component
@Converter
public class EncryptedStringConverter implements AttributeConverter<String, String> {
 

Transparent column encryption in Spring & JPA

encryption aes-gcm jpa-converter
Advanced 10 steps
typescript
import { Inject, Injectable, Logger } from '@nestjs/common';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
import { InjectRepository } from '@nestjs/typeorm';

A cache-aside country lookup in NestJS

cache-aside dependency-injection batch-lookup
Intermediate 8 steps
java
package com.acme.billing.config;
 
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;

Feature-flagged beans with Spring @ConditionalOnProperty

feature-flags conditional-beans strategy-pattern
Intermediate 5 steps
typescript
import { Injectable, effect, signal, computed } from '@angular/core';
 
interface Preferences {
  theme: 'light' | 'dark';

A signal-based preferences store in Angular

signals state-management persistence
Intermediate 7 steps

Share this explainer

Here's the card — post it anywhere.

How API versioning works in NestJS — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code