Waarom API-versiebeheer cruciaal is
Je SaaS draait, klanten zijn aangesloten via je API, en dan komt het moment: je moet een breaking change doorvoeren. Misschien moet je een veldnaam wijzigen, een endpoint herstructureren, of een compleet nieuw datamodel introduceren. Zonder een solide versiebeheerstrategie breek je de integraties van al je klanten in één keer.
API-versiebeheer is geen luxe — het is een vereiste zodra je eerste externe klant je API gebruikt. In dit artikel duiken we diep in de strategieën, patronen en praktische implementaties die je nodig hebt.
De drie hoofdstrategieën
1. URL-pad versiebeheer
De meest voorkomende en zichtbare aanpak:
GET /api/v1/users
GET /api/v2/users
Voordelen:
- Extreem duidelijk en zichtbaar
- Makkelijk te testen en te documenteren
- Caching werkt out-of-the-box (verschillende URLs = verschillende cache entries)
Nadelen:
- Kan leiden tot code-duplicatie als je niet oppast
- Klanten moeten URLs aanpassen bij een upgrade
Implementatie in Next.js:
// app/api/v1/users/route.ts
export async function GET() {
const users = await db.user.findMany({
select: { id: true, name: true, email: true }
});
return Response.json(users);
}
// app/api/v2/users/route.ts
export async function GET() {
const users = await db.user.findMany({
select: {
id: true,
firstName: true, // v2: gesplitst naam-veld
lastName: true,
email: true,
organization: { select: { id: true, name: true } }
}
});
return Response.json({ data: users, meta: { total: users.length } });
}
2. Header-gebaseerd versiebeheer
Gebruik een custom header om de versie aan te geven:
GET /api/users
Accept-Version: 2
Voordelen:
- Schone URLs die niet veranderen
- Makkelijk een standaardversie in te stellen
Nadelen:
- Minder zichtbaar — makkelijk te vergeten in documentatie
- Lastiger te testen in de browser
// middleware.ts
import { NextResponse } from 'next/server';
export function middleware(request: Request) {
const version = request.headers.get('Accept-Version') || '1';
const response = NextResponse.next();
response.headers.set('X-API-Version', version);
return response;
}
// In je route handler
export async function GET(request: Request) {
const version = request.headers.get('Accept-Version') || '1';
if (version === '2') {
return handleV2(request);
}
return handleV1(request);
}
3. Query parameter versiebeheer
GET /api/users?version=2
Simpel maar minder elegant. Werkt goed voor interne APIs, maar wordt afgeraden voor publieke APIs omdat het de URL vervuilt.
Het echte probleem: backward compatibility
Versiebeheer is slechts de helft van het verhaal. De echte uitdaging is backward compatibility — ervoor zorgen dat bestaande integraties blijven werken terwijl je API evolueert.
De gouden regels
1. Voeg toe, verwijder niet
// ✅ Goed: nieuw veld toevoegen
interface UserV1 {
id: string;
name: string;
email: string;
}
interface UserV2 extends UserV1 {
firstName: string; // Nieuw
lastName: string; // Nieuw
// 'name' blijft bestaan voor backward compatibility
}
2. Maak nieuwe velden optioneel
// ✅ Goed: optioneel nieuw veld in request body
interface CreateUserRequest {
name: string;
email: string;
organizationId?: string; // Nieuw, maar optioneel
}
3. Gebruik een deprecation-strategie
export async function GET(request: Request) {
const response = await getUsers();
// Markeer deprecated velden in de response headers
return Response.json(response, {
headers: {
'Deprecation': 'true',
'Sunset': 'Sat, 01 Jun 2026 00:00:00 GMT',
'Link': '</api/v2/users>; rel=successor-version'
}
});
}
Een versiebeheersysteem bouwen met de adapter-pattern
De krachtigste aanpak is het adapter-pattern: je bouwt één interne representatie en vertaalt die per versie.
// lib/api/adapters/user-adapter.ts
interface InternalUser {
id: string;
firstName: string;
lastName: string;
email: string;
organizationId: string;
createdAt: Date;
metadata: Record<string, unknown>;
}
// V1 adapter - het originele formaat
function toV1(user: InternalUser) {
return {
id: user.id,
name: `${user.firstName} ${user.lastName}`,
email: user.email,
};
}
// V2 adapter - uitgebreider formaat
function toV2(user: InternalUser) {
return {
id: user.id,
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
organizationId: user.organizationId,
createdAt: user.createdAt.toISOString(),
};
}
// V3 adapter - volledig formaat met metadata
function toV3(user: InternalUser) {
return {
...toV2(user),
metadata: user.metadata,
_links: {
self: `/api/v3/users/${user.id}`,
organization: `/api/v3/organizations/${user.organizationId}`,
}
};
}
const adapters: Record<string, (user: InternalUser) => unknown> = {
'1': toV1,
'2': toV2,
'3': toV3,
};
export function serializeUser(user: InternalUser, version: string) {
const adapter = adapters[version] || adapters['1'];
return adapter(user);
}
Migratiestrategie voor klanten
Een goede migratiestrategie is net zo belangrijk als de technische implementatie:
1. Communiceer vroegtijdig
// Stuur deprecation-waarschuwingen in response headers
// EN log welke klanten nog oude versies gebruiken
async function trackApiVersionUsage(
apiKey: string,
version: string,
endpoint: string
) {
await db.apiUsageLog.create({
data: {
apiKey,
version,
endpoint,
timestamp: new Date(),
}
});
}
2. Bied een migratieperiode
Een typisch tijdschema:
- Maand 1-2: Nieuwe versie beschikbaar, oude versie werkt volledig
- Maand 3-4: Deprecation-headers op oude versie, migratie-documentatie beschikbaar
- Maand 5: Waarschuwingsemails naar klanten die nog de oude versie gebruiken
- Maand 6: Oude versie wordt uitgeschakeld (met grace period)
3. Bied migratietools
// Een endpoint dat klanten helpt hun integratie te testen
// POST /api/migration/validate
export async function POST(request: Request) {
const body = await request.json();
const { fromVersion, toVersion, sampleRequest } = body;
// Simuleer de request tegen beide versies
const oldResponse = await simulateRequest(sampleRequest, fromVersion);
const newResponse = await simulateRequest(sampleRequest, toVersion);
// Toon de verschillen
return Response.json({
compatible: isCompatible(oldResponse, newResponse),
differences: getDifferences(oldResponse, newResponse),
migrationGuide: getMigrationSteps(fromVersion, toVersion),
});
}
Veelgemaakte fouten
❌ Te veel versies tegelijk ondersteunen
Elke versie die je onderhoudt kost tijd en energie. Streef naar maximaal 2-3 actieve versies.
❌ Geen sunset-beleid
Zonder duidelijk beleid over wanneer oude versies stoppen, bouw je technische schuld op die exponentieel groeit.
❌ Versiebeheer vergeten bij webhooks
Als je webhooks verstuurt, moeten die ook geversioned zijn:
async function sendWebhook(event: string, data: unknown, subscription: WebhookSubscription) {
const payload = serializeWebhookPayload(
event,
data,
subscription.apiVersion // Stuur het formaat dat de klant verwacht
);
await fetch(subscription.url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Version': subscription.apiVersion,
'X-Webhook-Signature': sign(payload),
},
body: JSON.stringify(payload),
});
}
❌ Geen changelog bijhouden
// Automatisch een changelog genereren vanuit je codebase
// changelog/v2.md
/**
* ## API v2 Changelog
*
* ### Breaking Changes
* - \`name\` veld gesplitst in \`firstName\` en \`lastName\`
* - Response wrapper: alle responses bevatten nu \`{ data, meta }\`
*
* ### New Features
* - Organization data beschikbaar via \`?include=organization\`
* - Pagination metadata in \`meta\` object
*
* ### Deprecated
* - \`name\` veld (nog beschikbaar, wordt verwijderd in v3)
*/
Conclusie
API-versiebeheer is een van die dingen die je liever te vroeg dan te laat implementeert. Begin met URL-pad versiebeheer (het is het meest expliciet), gebruik het adapter-pattern om code-duplicatie te voorkomen, en stel vanaf dag één een duidelijk deprecation-beleid op.
De beste API-versiebeheerstrategie is er eentje die je klanten nauwelijks opmerken — omdat de overgangen soepel zijn, de communicatie helder is, en de migratietools uitstekend werken.
De kernpunten:
- Kies één versiebeheerstrategie en wees consistent
- Voeg toe, verwijder niet — backward compatibility eerst
- Gebruik het adapter-pattern voor schone code
- Communiceer vroegtijdig en bied migratietools
- Maximaal 2-3 actieve versies tegelijk
- Vergeet webhooks niet te versionen