Perubahan API yang merusak adalah setara developer dengan menarik taplak meja — semuanya jatuh. Saya telah mengirimkan tiga versi utama API ERP sambil mempertahankan kompatibilitas backward untuk klien yang belum bermigrasi. Tooling di NestJS membuat versioning mudah diimplementasikan, tetapi bagian yang sulit adalah strategi migrasi: bagaimana Anda menghentikan v1 tanpa merusak integrasi?
NestJS 8+ memiliki versioning API native bawaan di framework, mendukung empat strategi: URI versioning (/v1/users, /v2/users), Header versioning (header permintaan kustom seperti X-API-Version), Media Type versioning (parameter versi header Accept), dan Custom versioning (fungsi apa pun yang mengekstrak versi dari permintaan). Anda mengaktifkan versioning dalam fungsi bootstrap dengan satu baris.
URI versioning menyematkan versi di jalur URL: `/api/v1/invoices`, `/api/v2/invoices`. Ini adalah strategi yang paling terlihat dan eksplisit — klien tahu versi mana yang mereka panggil dari URL saja, log mudah difilter berdasarkan versi, dan alat dokumentasi API (Swagger) dapat menghasilkan dokumen terpisah per versi.
Header versioning menjaga URL tetap bersih — `/api/invoices` terlepas dari versi — dan menggunakan header kustom seperti `X-API-Version: 2` untuk memilih versi. Saya menggunakan URI versioning untuk API eksternal dan header versioning untuk API internal layanan-ke-layanan di mana saya mengontrol kedua sisi.
NestJS API Versioning Strategies:
──────────────────────────────────────────────────────────
1. URI Versioning (recommended for public APIs)
GET /api/v1/invoices → InvoicesV1Controller
GET /api/v2/invoices → InvoicesV2Controller
✓ Visible, explicit, easy to test in browser
2. Header Versioning (for service-to-service)
GET /api/invoices
X-API-Version: 2 → InvoicesV2Controller
✓ Clean URLs, ✗ less discoverable
3. VERSION_NEUTRAL (for stable shared endpoints)
GET /api/health → responds to ANY version
GET /api/docs → responds to ANY version
Migration lifecycle:
Week 0: Ship v2 alongside v1 (both work)
Week 1-4: Document v1 as deprecated, send emails
Month 2: Add Sunset header to v1 responses
Month 3: Log v1 usage per API key/user
Month 5: Return 429 for v1 requests with migration link
Month 6: Remove v1 codeDari pengalaman saya mengelola versi API di proyek ERP: gunakan VERSION_NEUTRAL untuk endpoint bersama yang tidak berubah antar versi. Daripada menduplikasi handler rute di setiap controller versi, tandai endpoint stabil dengan `@Version(VERSION_NEUTRAL)` — mereka merespons permintaan terlepas dari versi apa yang ditentukan klien. Endpoint GET /api/health, misalnya, tidak memerlukan varian v1/v2.
Pendekatan terbersih untuk API NestJS yang diversion adalah menjaga logika bisnis di lapisan layanan dan mengeksposnya melalui controller spesifik versi. Controller Version 1 dan Version 2 dapat berbagi layanan yang sama, hanya memanggil metode yang berbeda atau meneruskan DTO yang berbeda. Ini mencegah antipattern menduplikasi logika bisnis di seluruh controller versi.
Saat menggunakan @nestjs/swagger dengan API yang diversion, konfigurasikan dokumen Swagger terpisah per versi API. Setiap DocumentBuilder mendapatkan base path sendiri — `/api/v1` untuk v1, `/api/v2` untuk v2. Ini memberi Anda UI Swagger /api-docs/v1 dan /api-docs/v2 yang terpisah.
// main.ts — enable URI versioning
import { VersioningType } from '@nestjs/common'
async function bootstrap() {
const app = await NestFactory.create(AppModule)
app.enableVersioning({ type: VersioningType.URI })
await app.listen(3000)
}
// invoices-v1.controller.ts
import { Controller, Get, Version, VERSION_NEUTRAL } from '@nestjs/common'
@Controller({ path: 'invoices', version: '1' })
export class InvoicesV1Controller {
constructor(private readonly invoicesService: InvoicesService) {}
@Get()
findAll() {
return this.invoicesService.findAllV1() // old response shape
}
@Get('health')
@Version(VERSION_NEUTRAL) // responds to all versions
health() {
return { status: 'ok' }
}
}
// invoices-v2.controller.ts — extended with pagination
@Controller({ path: 'invoices', version: '2' })
export class InvoicesV2Controller {
constructor(private readonly invoicesService: InvoicesService) {}
@Get()
findAll(@Query('cursor') cursor?: string, @Query('limit') limit = 20) {
return this.invoicesService.findAllV2({ cursor, limit }) // cursor pagination
}
}
// Sunset header middleware for v1 deprecation
@Injectable()
export class DeprecationMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: () => void) {
if (req.url.includes('/v1/')) {
res.setHeader('Deprecation', 'true')
res.setHeader('Sunset', 'Sat, 01 Jan 2027 00:00:00 GMT')
res.setHeader('Link', '<https://api.example.com/v2/>; rel="successor-version"')
}
next()
}
}Router NestJS mencocokkan rute dalam urutan spesifisitas. Jika tidak ada controller v2 yang ada untuk rute itu, NestJS tidak fallback ke v1 — mengembalikan 404. Ini berarti Anda harus secara eksplisit mengimplementasikan setiap endpoint yang diversion. Untuk migrasi bertahap di mana v2 sebagian besar sama dengan v1, buat controller v2 yang memperluas controller v1 dan hanya menimpa metode yang berubah.
Kesalahan versioning API terbesar adalah menghapus rute v1 sebelum semua klien pindah ke v2. Bahkan jika Anda telah mengkomunikasikan tanggal penghentian, integrasi rusak. Pendekatan yang aman: tambahkan catatan `@Deprecated()` di dokumen Swagger v1, tambahkan header respons seperti `Deprecation: true; date='2026-01-01'`, catat penggunaan endpoint yang tidak digunakan lagi, dan hanya hapus ketika penggunaan turun ke nol.
Proses penghentian API saya: 1) Kirim v2 bersama v1 — keduanya berfungsi penuh. 2) Perbarui dokumen API untuk menandai endpoint v1 sebagai tidak digunakan lagi dengan panduan migrasi. 3) Tambahkan header Sunset ke respons v1. 4) Tambahkan logging untuk menghitung panggilan endpoint v1 per klien. 5) Email klien yang masih menggunakan v1 dengan tenggat waktu migrasi. 6) Seminggu sebelum sunset, kembalikan error 429 untuk permintaan v1. 7) Hapus kode v1 pada tanggal sunset.
API internal layanan-ke-layanan dalam monorepo atau sistem microservices tidak memerlukan disiplin versioning yang sama dengan API eksternal. Jika Anda mengontrol produsen dan semua konsumen, Anda dapat menggunakan semantic versioning dengan deployment terkoordinasi. API eksternal (dikonsumsi oleh aplikasi mobile, integrasi pihak ketiga, atau pengembang publik) memerlukan versioning ketat, jendela penghentian yang panjang, dan jaminan kompatibilitas backward.