Terug naar blog
apiversiebeheerbackward-compatibilitysaas-architectuurintegraties

API-versiebeheer en backward compatibility: zo laat je klantintegraties niet stuk gaan

Door SaaS Masters22 maart 20266 min leestijd

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