--- trigger: always_on --- # API Versioning Strategy ## When to Use This Skill Use this skill when: - Versioning APIs - Handling breaking changes - Implementing API deprecation - Maintaining backward compatibility - Implementing version negotiation - Managing multiple API versions - Planning API evolution - Communicating API changes to consumers ## Core Concepts ### Versioning Strategies 1. **URL Path Versioning**: `/api/v1/users`, `/api/v2/users` 2. **Header Versioning**: `Accept: application/vnd.goodgo.v1+json` 3. **Query Parameter**: `/api/users?version=1` 4. **Semantic Versioning**: Major.Minor.Patch (e.g., 1.2.3) ### Compatibility Types - **Backward Compatible**: New version works with old clients - **Forward Compatible**: Old version works with new clients - **Breaking Changes**: Incompatible changes requiring new version ## URL Path Versioning ### Implementation ```typescript // src/routes/index.ts // EN: Route versioning // VI: Route versioning import { Router } from 'express'; import v1Router from './v1'; import v2Router from './v2'; const router = Router(); // EN: Version 1 routes // VI: Routes version 1 router.use('/v1', v1Router); // EN: Version 2 routes // VI: Routes version 2 router.use('/v2', v2Router); export default router; ``` ### Version Router ```typescript // src/routes/v1/index.ts // EN: Version 1 routes // VI: Routes version 1 import { Router } from 'express'; import userRoutes from './users'; const router = Router(); router.use('/users', userRoutes); export default router; // src/routes/v2/index.ts // EN: Version 2 routes with breaking changes // VI: Routes version 2 với breaking changes import { Router } from 'express'; import userRoutes from './users'; // EN: Different implementation / VI: Implementation khác const router = Router(); router.use('/users', userRoutes); // EN: Different response format / VI: Format response khác export default router; ``` ## Header-Based Versioning ### Version Negotiation Middleware ```typescript // src/middlewares/version-negotiation.middleware.ts // EN: Version negotiation middleware // VI: Middleware version negotiation import { Request, Response, NextFunction } from 'express'; import { logger } from '@goodgo/logger'; export function versionNegotiation( req: Request, res: Response, next: NextFunction ): void { // EN: Extract version from Accept header // VI: Trích xuất version từ Accept header const acceptHeader = req.headers.accept || ''; const versionMatch = acceptHeader.match(/application\/vnd\.goodgo\.v(\d+)\+json/); if (versionMatch) { const requestedVersion = parseInt(versionMatch[1], 10); req.apiVersion = requestedVersion; // EN: Check if version is supported // VI: Kiểm tra xem version có được hỗ trợ không const supportedVersions = [1, 2]; if (!supportedVersions.includes(requestedVersion)) { return res.status(400).json({ success: false, error: { code: 'UNSUPPORTED_VERSION', message: `API version ${requestedVersion} is not supported. Supported versions: ${supportedVersions.join(', ')}`, }, }); } } else { // EN: Default to latest version // VI: Mặc định version mới nhất req.apiVersion = 2; } next(); } ``` ### Version-Aware Controller ```typescript // src/modules/user/user.controller.ts // EN: Version-aware controller // VI: Controller nhận biết version export class UserController { async getUser(req: Request, res: Response): Promise { const version = req.apiVersion || 2; if (version === 1) { // EN: Version 1 response format // VI: Format response version 1 const user = await this.userService.findById(req.params.id); res.json({ id: user.id, email: user.email, name: user.name, }); } else { // EN: Version 2 response format // VI: Format response version 2 const user = await this.userService.findById(req.params.id); res.json({ success: true, data: { user: { id: user.id, email: user.email, name: user.name, profile: user.profile, // EN: New field in v2 / VI: Field mới trong v2 }, }, metadata: { version: '2.0.0', timestamp: new Date().toISOString(), }, }); } } } ``` ## Semantic Versioning ### Version Structure ``` MAJOR.MINOR.PATCH MAJOR: Breaking changes MINOR: Backward-compatible additions PATCH: Backward-compatible bug fixes ``` ### Version Response ```typescript // src/core/api/version.middleware.ts // EN: Add version to response // VI: Thêm version vào response export function versionMiddleware(req: Request, res: Response, next: NextFunction): void { const originalJson = res.json.bind(res); res.json = (data: any) => { const response = { ...data, metadata: { ...data.metadata, apiVersion: req.apiVersion || '2.0.0', serviceVersion: process.env.SERVICE_VERSION || '1.0.0', }, }; return originalJson(response); }; next(); } ``` ## API Deprecation ### Deprecation Headers ```typescript // src/middlewares/deprecation.middleware.ts // EN: Deprecation warning middleware // VI: Middleware cảnh báo deprecation export function deprecationMiddleware(version: string, sunsetDate: string) { return (req: Request, res: Response, next: NextFunction): void => { if (req.apiVersion && parseInt(req.apiVersion.toString()) < parseInt(version)) { res.setHeader('Deprecation', 'true'); res.setHeader('Sunset', sunsetDate); res.setHeader('Link', `<${req.url.replace(/\/v\d+/, `/v${version}`)}>; rel="successor-version"`); res.setHeader('Warning', `299 - "API version ${req.apiVersion} is deprecated. Please migrate to version ${version} by ${sunsetDate}"`); } next(); }; } // Usage router.use('/v1', deprecationMiddleware('2', '2024-12-31'), v1Router); ``` ### Deprecation Documentation ```typescript // src/docs/deprecation.md // EN: Deprecation notices // VI: Thông báo deprecation ## API Version 1 Deprecation **Deprecated**: 2024-01-01 **Sunset Date**: 2024-12-31 **Migration Guide**: [Migration Guide](./migration-v1-to-v2.md) ### Changes in Version 2 - New response format - Additional user profile fields - Updated authentication flow ``` ## Backward Compatibility ### Compatibility Layer ```typescript // src/core/api/compatibility.adapter.ts // EN: Backward compatibility adapter // VI: Adapter tương thích ngược export class CompatibilityAdapter { /** * EN: Adapt v1 response to v2 format * VI: Adapt response v1 sang format v2 */ adaptV1ToV2(v1Data: any): any { return { success: true, data: { user: { ...v1Data, profile: null, // EN: Add default for new field / VI: Thêm mặc định cho field mới }, }, metadata: { version: '2.0.0', adapted: true, }, }; } /** * EN: Adapt v2 request to v1 format * VI: Adapt request v2 sang format v1 */ adaptV2RequestToV1(v2Request: any): any { return { email: v2Request.email, name: v2Request.name, // EN: Ignore new fields / VI: Bỏ qua các field mới }; } } ``` ## Breaking Changes Migration ### Migration Strategy ```typescript // src/core/api/migration.strategy.ts // EN: Migration strategy for breaking changes // VI: Chiến lược migration cho breaking changes export class MigrationStrategy { /** * EN: Phase 1: Support both versions * VI: Giai đoạn 1: Hỗ trợ cả hai versions */ phase1SupportBoth(): void { // EN: Keep v1, add v2 // VI: Giữ v1, thêm v2 router.use('/v1', v1Router); router.use('/v2', v2Router); } /** * EN: Phase 2: Deprecate v1 * VI: Giai đoạn 2: Deprecate v1 */ phase2DeprecateV1(): void { router.use('/v1', deprecationMiddleware('2', '2024-12-31'), v1Router); router.use('/v2', v2Router); } /** * EN: Phase 3: Remove v1 * VI: Giai đoạn 3: Xóa v1 */ phase3RemoveV1(): void { // EN: After sunset date, remove v1 routes // VI: Sau sunset date, xóa v1 routes router.use('/v2', v2Router); } } ``` ## Best Practices 1. **Versioning Strategy**: Choose URL path or header, be consistent 2. **Semantic Versioning**: Use MAJOR.MINOR.PATCH 3. **Deprecation**: Always deprecate before removing 4. **Migration Guide**: Provide clear migration documentation 5. **Backward Compatibility**: Maintain compatibility when possible 6. **Communication**: Clearly communicate version changes ## Common Mistakes 1. **No Deprecation Period**: Breaking clients suddenly ```typescript // ❌ BAD: Remove v1 immediately router.use('/v2', v2Router); // ✅ GOOD: Deprecate with sunset date router.use('/v1', deprecationMiddleware('2', '2024-12-31'), v1Router); router.use('/v2', v2Router); ``` 2. **Breaking Changes Without Major Version**: Client confusion ``` # ❌ BAD: Breaking change in minor version v1.1.0 → Changed response format # ✅ GOOD: Breaking change = new major version v1.x.x → v2.0.0 (new response format) ``` 3. **Inconsistent Versioning Strategy**: Mixed approaches ```typescript // ❌ BAD: Mix URL and header versioning /api/v1/users + Accept: application/vnd.v2+json // ✅ GOOD: Choose one approach /api/v1/users OR Accept: application/vnd.goodgo.v1+json ``` 4. **No Migration Guide**: Clients don't know how to upgrade ``` # ✅ Always provide: - Changelog - Migration guide - Sunset date - Deprecation warnings ``` ## Quick Reference | Strategy | Pros | Cons | Use When | |----------|------|------|----------| | **URL Path** | Clear, cacheable | URL changes | Public APIs | | **Header** | Clean URLs | Less visible | Internal APIs | | **Query Param** | Simple | Not RESTful | Quick prototypes | **Semantic Versioning:** ``` MAJOR.MINOR.PATCH │ │ └── Bug fixes (backward compatible) │ └──────── New features (backward compatible) └────────────── Breaking changes ``` **Version Lifecycle:** ``` v1 Active → v2 Released → v1 Deprecated → v1 Sunset → v1 Removed │ │ │ │ │ │ │ Add headers Remove from Delete │ Support + warnings docs routes Solo both ``` **Deprecation Headers:** ```http Deprecation: true Sunset: Sat, 31 Dec 2024 23:59:59 GMT Warning: 299 - "API v1 is deprecated. Migrate to v2 by 2024-12-31" Link: ; rel="successor-version" ``` **Version Detection:** ```typescript // URL path const version = req.path.match(/\/v(\d+)\//)?.[1] || '2'; // Header const version = req.headers.accept?.match(/vnd\.goodgo\.v(\d+)/)?.[1]; ```