Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
40 prioritized NestJS best practices across architecture, DI, security, performance, testing, and microservices.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
rules/api-versioning.md
1---2title: Use API Versioning for Breaking Changes3impact: MEDIUM4impactDescription: Versioning allows you to evolve APIs without breaking existing clients5tags: api, versioning, breaking-changes, compatibility6---78## Use API Versioning for Breaking Changes910Use NestJS built-in versioning when making breaking changes to your API. Choose a versioning strategy (URI, header, or media type) and apply it consistently. This allows old clients to continue working while new clients use updated endpoints.1112**Incorrect (breaking changes without versioning):**1314```typescript15// Breaking changes without versioning16@Controller('users')17export class UsersController {18@Get(':id')19async findOne(@Param('id') id: string): Promise<User> {20// Original response: { id, name, email }21// Later changed to: { id, firstName, lastName, emailAddress }22// Old clients break!23return this.usersService.findOne(id);24}25}2627// Manual versioning in routes28@Controller('v1/users')29export class UsersV1Controller {}3031@Controller('v2/users')32export class UsersV2Controller {}33// Inconsistent, error-prone, hard to maintain34```3536**Correct (use NestJS built-in versioning):**3738```typescript39// Enable versioning in main.ts40async function bootstrap() {41const app = await NestFactory.create(AppModule);4243// URI versioning: /v1/users, /v2/users44app.enableVersioning({45type: VersioningType.URI,46defaultVersion: '1',47});4849// Or header versioning: X-API-Version: 150app.enableVersioning({51type: VersioningType.HEADER,52header: 'X-API-Version',53defaultVersion: '1',54});5556// Or media type: Accept: application/json;v=157app.enableVersioning({58type: VersioningType.MEDIA_TYPE,59key: 'v=',60defaultVersion: '1',61});6263await app.listen(3000);64}6566// Version-specific controllers67@Controller('users')68@Version('1')69export class UsersV1Controller {70@Get(':id')71async findOne(@Param('id') id: string): Promise<UserV1Response> {72const user = await this.usersService.findOne(id);73// V1 response format74return {75id: user.id,76name: user.name,77email: user.email,78};79}80}8182@Controller('users')83@Version('2')84export class UsersV2Controller {85@Get(':id')86async findOne(@Param('id') id: string): Promise<UserV2Response> {87const user = await this.usersService.findOne(id);88// V2 response format with breaking changes89return {90id: user.id,91firstName: user.firstName,92lastName: user.lastName,93emailAddress: user.email,94createdAt: user.createdAt,95};96}97}9899// Per-route versioning - different versions for different routes100@Controller('users')101export class UsersController {102@Get()103@Version('1')104findAllV1(): Promise<UserV1Response[]> {105return this.usersService.findAllV1();106}107108@Get()109@Version('2')110findAllV2(): Promise<UserV2Response[]> {111return this.usersService.findAllV2();112}113114@Get(':id')115@Version(['1', '2']) // Same handler for multiple versions116findOne(@Param('id') id: string): Promise<User> {117return this.usersService.findOne(id);118}119120@Post()121@Version(VERSION_NEUTRAL) // Available in all versions122create(@Body() dto: CreateUserDto): Promise<User> {123return this.usersService.create(dto);124}125}126127// Shared service with version-specific logic128@Injectable()129export class UsersService {130async findOne(id: string, version: string): Promise<any> {131const user = await this.repo.findOne({ where: { id } });132133if (version === '1') {134return this.toV1Response(user);135}136return this.toV2Response(user);137}138139private toV1Response(user: User): UserV1Response {140return {141id: user.id,142name: `${user.firstName} ${user.lastName}`,143email: user.email,144};145}146147private toV2Response(user: User): UserV2Response {148return {149id: user.id,150firstName: user.firstName,151lastName: user.lastName,152emailAddress: user.email,153createdAt: user.createdAt,154};155}156}157158// Controller extracts version159@Controller('users')160export class UsersController {161@Get(':id')162async findOne(163@Param('id') id: string,164@Headers('X-API-Version') version: string = '1',165): Promise<any> {166return this.usersService.findOne(id, version);167}168}169170// Deprecation strategy - mark old versions as deprecated171@Controller('users')172@Version('1')173@UseInterceptors(DeprecationInterceptor)174export class UsersV1Controller {175// All V1 routes will include deprecation warning176}177178@Injectable()179export class DeprecationInterceptor implements NestInterceptor {180intercept(context: ExecutionContext, next: CallHandler): Observable<any> {181const response = context.switchToHttp().getResponse();182response.setHeader('Deprecation', 'true');183response.setHeader('Sunset', 'Sat, 1 Jan 2025 00:00:00 GMT');184response.setHeader('Link', '</v2/users>; rel="successor-version"');185186return next.handle();187}188}189```190191Reference: [NestJS Versioning](https://docs.nestjs.com/techniques/versioning)192