2 min read
BackendAPI Versioning Strategies
API Versioning implementieren. URL Versioning, Header Versioning und Breaking Changes ohne Downtime managen.
API VersioningSemantic VersioningBreaking ChangesBackward CompatibilityAPI EvolutionDeprecation

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
// 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
// lib/api/version-router.ts
import { NextRequest, NextResponse } from 'next/server';
interface VersionConfig {
current: string;
supported: string[];
deprecated: string[];
sunset: Record<string, Date>;
}
const versionConfig: VersionConfig = {
current: 'v3',
supported: ['v2', 'v3'],
deprecated: ['v1'],
sunset: {
v1: new Date('2024-06-01')
}
};
type VersionHandler = (req: NextRequest) => Promise<NextResponse>;
interface VersionedHandlers {
v1?: VersionHandler;
v2?: VersionHandler;
v3?: VersionHandler;
}
export function versionedRoute(handlers: VersionedHandlers) {
return async (req: NextRequest): Promise<NextResponse> => {
// 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',
`</api/${versionConfig.current}>; 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
// 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
// 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<void> {
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<void> {
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<string, string> {
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
// 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
// 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<T>(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<User[]> {
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:
- Strategie: URL-based ist am klarsten
- Kommunikation: Frühe Deprecation Notices
- Support: Alte Versionen lang genug pflegen
- Migration: Klare Guides und Tools
Gutes Versioning ermöglicht Evolution ohne Client-Breakage.
Bildprompts
- "API versioning timeline, version lifecycle visualization"
- "Breaking changes migration flowchart, before and after"
- "Deprecation warning email template, sunset date highlighted"