# API Versioning Strategies **Meta-Description:** API Versioning implementieren. URL Versioning, Header Versioning und Breaking Changes ohne Downtime managen. **Keywords:** API Versioning, Semantic Versioning, Breaking Changes, Backward Compatibility, API Evolution, Deprecation --- ## Einführung **API Versioning** ermöglicht Evolution ohne Breaking Changes für bestehende Clients. Von **URL-based** bis **Header-based** – verschiedene Strategien haben unterschiedliche Trade-offs. Dieser Guide zeigt Best Practices für nachhaltige API-Entwicklung. --- ## Versioning Strategies ``` ┌─────────────────────────────────────────────────────────────┐ │ API VERSIONING STRATEGIES │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 1. URL PATH VERSIONING (Recommended): │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ /api/v1/users │ │ │ │ /api/v2/users │ │ │ │ │ │ │ │ ✓ Explicit, easy to understand │ │ │ │ ✓ Easy to route and cache │ │ │ │ ✓ Can run versions in parallel │ │ │ │ ✗ URL changes for version bumps │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ 2. HEADER VERSIONING: │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ Accept: application/vnd.myapi.v2+json │ │ │ │ X-API-Version: 2 │ │ │ │ │ │ │ │ ✓ Clean URLs │ │ │ │ ✓ Follows HTTP semantics │ │ │ │ ✗ Harder to test (need headers) │ │ │ │ ✗ Not visible in URL │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ 3. QUERY PARAMETER: │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ /api/users?version=2 │ │ │ │ │ │ │ │ ✓ Easy to implement │ │ │ │ ✗ Optional parameter = default version issues │ │ │ │ ✗ Caching complications │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ 4. DATE-BASED VERSIONING (Stripe-style): │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ Stripe-Version: 2024-01-15 │ │ │ │ │ │ │ │ ✓ Fine-grained control │ │ │ │ ✓ Clear timeline │ │ │ │ ✗ Many versions to maintain │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ Semantic Versioning (for SDKs): │ │ ├── MAJOR.MINOR.PATCH (1.2.3) │ │ ├── MAJOR: Breaking changes │ │ ├── MINOR: New features, backward compatible │ │ └── PATCH: Bug fixes │ │ │ └─────────────────────────────────────────────────────────────┘ ``` --- ## URL Versioning Implementation ```typescript // app/api/v1/users/route.ts import { NextResponse } from 'next/server'; export async function GET() { // V1 response format const users = await db.user.findMany({ select: { id: true, name: true, email: true } }); return NextResponse.json({ users, // Array at root level (v1 format) count: users.length }); } // app/api/v2/users/route.ts export async function GET(request: Request) { const { searchParams } = new URL(request.url); const page = parseInt(searchParams.get('page') || '1'); const limit = parseInt(searchParams.get('limit') || '20'); const [users, total] = await Promise.all([ db.user.findMany({ skip: (page - 1) * limit, take: limit, select: { id: true, name: true, email: true, createdAt: true, // New field in v2 avatar: true // New field in v2 } }), db.user.count() ]); // V2 response format (standardized) return NextResponse.json({ data: users, pagination: { page, limit, total, totalPages: Math.ceil(total / limit) } }); } ``` --- ## Version Router ```typescript // lib/api/version-router.ts import { NextRequest, NextResponse } from 'next/server'; interface VersionConfig { current: string; supported: string[]; deprecated: string[]; sunset: Record; } const versionConfig: VersionConfig = { current: 'v3', supported: ['v2', 'v3'], deprecated: ['v1'], sunset: { v1: new Date('2024-06-01') } }; type VersionHandler = (req: NextRequest) => Promise; interface VersionedHandlers { v1?: VersionHandler; v2?: VersionHandler; v3?: VersionHandler; } export function versionedRoute(handlers: VersionedHandlers) { return async (req: NextRequest): Promise => { // Extract version from URL const version = extractVersion(req.nextUrl.pathname); // Check if version is supported if (!versionConfig.supported.includes(version) && !versionConfig.deprecated.includes(version)) { return NextResponse.json( { error: `API version ${version} is not supported` }, { status: 400 } ); } // Get handler for version const handler = handlers[version as keyof VersionedHandlers]; if (!handler) { return NextResponse.json( { error: `No handler for version ${version}` }, { status: 501 } ); } // Execute handler const response = await handler(req); // Add deprecation headers if needed if (versionConfig.deprecated.includes(version)) { const sunsetDate = versionConfig.sunset[version]; response.headers.set('Deprecation', 'true'); response.headers.set('Sunset', sunsetDate.toUTCString()); response.headers.set( 'Link', `; rel="successor-version"` ); } // Add version header response.headers.set('X-API-Version', version); return response; }; } function extractVersion(pathname: string): string { const match = pathname.match(/\/api\/(v\d+)\//); return match ? match[1] : versionConfig.current; } // Usage export const GET = versionedRoute({ v1: async (req) => { // V1 implementation return NextResponse.json({ format: 'v1' }); }, v2: async (req) => { // V2 implementation return NextResponse.json({ data: [], format: 'v2' }); }, v3: async (req) => { // V3 implementation return NextResponse.json({ data: [], meta: {}, format: 'v3' }); } }); ``` --- ## Breaking Changes Management ```typescript // lib/api/breaking-changes.ts interface BreakingChange { id: string; version: string; date: Date; description: string; migration: string; affectedEndpoints: string[]; } export const breakingChanges: BreakingChange[] = [ { id: 'bc-001', version: 'v2', date: new Date('2024-01-15'), description: 'Response format changed to use data wrapper', migration: ` // Before (v1): const users = response.users; // After (v2): const users = response.data; `, affectedEndpoints: ['/users', '/projects', '/tasks'] }, { id: 'bc-002', version: 'v2', date: new Date('2024-01-15'), description: 'Pagination parameters renamed', migration: ` // Before (v1): GET /users?offset=0&count=20 // After (v2): GET /users?page=1&limit=20 `, affectedEndpoints: ['/users', '/projects'] }, { id: 'bc-003', version: 'v3', date: new Date('2024-06-01'), description: 'Date format changed to ISO 8601', migration: ` // Before (v2): { "created": "2024-01-15 10:30:00" } // After (v3): { "createdAt": "2024-01-15T10:30:00.000Z" } `, affectedEndpoints: ['*'] } ]; // Helper to check compatibility export function getBreakingChanges( fromVersion: string, toVersion: string ): BreakingChange[] { const fromNum = parseInt(fromVersion.replace('v', '')); const toNum = parseInt(toVersion.replace('v', '')); return breakingChanges.filter(change => { const changeNum = parseInt(change.version.replace('v', '')); return changeNum > fromNum && changeNum <= toNum; }); } ``` --- ## Deprecation Workflow ```typescript // lib/api/deprecation.ts import { db } from '@/lib/db'; interface DeprecationNotice { endpoint: string; version: string; deprecatedAt: Date; sunsetAt: Date; replacement?: string; reason: string; } const deprecationNotices: DeprecationNotice[] = [ { endpoint: '/api/v1/users', version: 'v1', deprecatedAt: new Date('2024-01-01'), sunsetAt: new Date('2024-06-01'), replacement: '/api/v2/users', reason: 'Migrating to standardized response format' } ]; // Track deprecated endpoint usage export async function trackDeprecatedUsage( apiKeyId: string, endpoint: string, version: string ): Promise { await db.deprecatedUsage.upsert({ where: { apiKeyId_endpoint_version: { apiKeyId, endpoint, version } }, update: { lastUsedAt: new Date(), usageCount: { increment: 1 } }, create: { apiKeyId, endpoint, version, usageCount: 1, firstUsedAt: new Date(), lastUsedAt: new Date() } }); } // Send deprecation warnings to users export async function sendDeprecationWarnings(): Promise { const activeDeprecations = deprecationNotices.filter( d => d.sunsetAt > new Date() ); for (const notice of activeDeprecations) { // Find users still using deprecated endpoints const usages = await db.deprecatedUsage.findMany({ where: { endpoint: notice.endpoint, version: notice.version, lastUsedAt: { gte: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) // Last 7 days } }, include: { apiKey: { include: { user: true } } } }); for (const usage of usages) { await sendEmail(usage.apiKey.user.email, 'api-deprecation-warning', { endpoint: notice.endpoint, currentVersion: notice.version, replacement: notice.replacement, sunsetDate: notice.sunsetAt, usageCount: usage.usageCount }); } } } // Middleware to add deprecation headers export function deprecationMiddleware( endpoint: string, version: string ): Record { const notice = deprecationNotices.find( d => d.endpoint === endpoint && d.version === version ); if (!notice) return {}; return { 'Deprecation': notice.deprecatedAt.toUTCString(), 'Sunset': notice.sunsetAt.toUTCString(), 'Link': notice.replacement ? `<${notice.replacement}>; rel="successor-version"` : '' }; } ``` --- ## Migration Guide Generator ```typescript // lib/api/migration-guide.ts export function generateMigrationGuide( fromVersion: string, toVersion: string ): string { const changes = getBreakingChanges(fromVersion, toVersion); let guide = `# Migration Guide: ${fromVersion} to ${toVersion}\n\n`; guide += `This guide covers all breaking changes when upgrading from API ${fromVersion} to ${toVersion}.\n\n`; guide += `## Summary\n\n`; guide += `- ${changes.length} breaking changes\n`; guide += `- Affected endpoints: ${[...new Set(changes.flatMap(c => c.affectedEndpoints))].join(', ')}\n\n`; guide += `## Changes\n\n`; changes.forEach((change, index) => { guide += `### ${index + 1}. ${change.description}\n\n`; guide += `**Version:** ${change.version}\n`; guide += `**Date:** ${change.date.toISOString().split('T')[0]}\n`; guide += `**Affected Endpoints:** ${change.affectedEndpoints.join(', ')}\n\n`; guide += `#### Migration\n\n`; guide += '```typescript\n'; guide += change.migration; guide += '\n```\n\n'; }); guide += `## Need Help?\n\n`; guide += `If you encounter issues during migration, please:\n`; guide += `- Check our [API documentation](/docs/api/${toVersion})\n`; guide += `- Contact support at api-support@example.com\n`; return guide; } ``` --- ## Client SDK Versioning ```typescript // sdk/src/versioned-client.ts type APIVersion = 'v1' | 'v2' | 'v3'; interface VersionedClientConfig { apiKey: string; version?: APIVersion; baseUrl?: string; } export class VersionedClient { private version: APIVersion; private baseUrl: string; constructor(config: VersionedClientConfig) { this.version = config.version || 'v3'; // Default to latest this.baseUrl = config.baseUrl || `https://api.example.com/${this.version}`; } // Response transformers for backward compatibility private transformResponse(data: unknown): T { if (this.version === 'v1') { // Transform v3 response to v1 format return this.transformToV1(data) as T; } if (this.version === 'v2') { return this.transformToV2(data) as T; } return data as T; } private transformToV1(data: any): any { // Remove data wrapper, convert dates, etc. if (data.data) { return { ...data.data, count: data.pagination?.total }; } return data; } private transformToV2(data: any): any { // Keep data wrapper but adjust format return data; } async getUsers(): Promise { const response = await this.request('/users'); return this.transformResponse(response); } } // Factory for version-specific clients export function createClient(version: APIVersion, apiKey: string) { switch (version) { case 'v1': console.warn('API v1 is deprecated. Please upgrade to v3.'); return new VersionedClient({ apiKey, version: 'v1' }); case 'v2': return new VersionedClient({ apiKey, version: 'v2' }); case 'v3': default: return new VersionedClient({ apiKey, version: 'v3' }); } } ``` --- ## Best Practices | Aspect | Recommendation | |--------|----------------| | **Format** | URL versioning (most common) | | **Increment** | Only for breaking changes | | **Support** | Min 12-18 months per version | | **Communication** | 6+ months deprecation notice | | **Headers** | Deprecation & Sunset headers | | **Docs** | Version-specific documentation | --- ## Fazit API Versioning erfordert: 1. **Strategie**: URL-based ist am klarsten 2. **Kommunikation**: Frühe Deprecation Notices 3. **Support**: Alte Versionen lang genug pflegen 4. **Migration**: Klare Guides und Tools Gutes Versioning ermöglicht Evolution ohne Client-Breakage. --- ## Bildprompts 1. "API versioning timeline, version lifecycle visualization" 2. "Breaking changes migration flowchart, before and after" 3. "Deprecation warning email template, sunset date highlighted" --- ## Quellen - [Stripe API Versioning](https://stripe.com/docs/api/versioning) - [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md) - [API Versioning Best Practices](https://www.freecodecamp.org/news/how-to-version-a-rest-api/) - [Semantic Versioning](https://semver.org/)