API Versioning trong NestJS
🧩 API Versioning trong NestJS – Thiết kế API bền vững và dễ mở rộng
Một trong những sai lầm phổ biến khi xây dựng API là bỏ qua versioning ngay từ đầu. Hệ quả là khi cần cập nhật hoặc refactor, chúng ta buộc phải break backward compatibility. May mắn thay, với NestJS – một framework NodeJS hiện đại – việc thêm versioning là cực kỳ dễ dàng, gọn gàng, và linh hoạt.
💡 Vì sao cần API Versioning?
Giả sử bạn phát triển một API công khai, được client từ mobile app và web app gọi mỗi ngày. Một ngày đẹp trời, bạn cần thay đổi schema trả về, hoặc logic xử lý business quan trọng. Nếu không có versioning, client cũ sẽ bị "vỡ".
Versioning giúp:
- Thay đổi API mà không phá vỡ client cũ
- Triển khai tính năng mới dần dần
- Giảm chi phí bảo trì
- Tạo tiền đề cho kiến trúc microservices hoặc phân phối API gateway
🧭 Khi nào nên bắt đầu áp dụng versioning?
Một sai lầm phổ biến là nghĩ rằng “cứ để API ổn định đã, rồi tính đến versioning sau.” Trên thực tế, việc không chuẩn bị versioning từ đầu sẽ khiến bạn phải tốn công refactor toàn bộ hệ thống khi cần mở rộng.
Lời khuyên:
-
Ngay cả khi bạn chỉ có 1 phiên bản API, vẫn nên thiết kế để "có chỗ" thêm version sau này.
-
Với team lớn hoặc nhiều frontend/mobile app cùng dùng API, hãy ưu tiên versioning ngay từ sprint đầu tiên.
🛠️ Các chiến lược versioning phổ biến
1. URI Versioning – Thêm phiên bản vào đường dẫn
Ví dụ:
GET /v1/products
GET /v2/products
✅ Ưu điểm:
- Rõ ràng, dễ debug
- Phổ biến, dễ triển khai với API Gateway, CDN
🚫 Nhược điểm:
- Vi phạm nguyên tắc RESTful "same URI for same resource"
2. Header Versioning – Đặt version trong HTTP Header
X-API-Version: 1
✅ Ưu điểm:
- RESTful hơn, URI sạch
- Phù hợp với internal API hoặc microservice
🚫 Nhược điểm:
- Yêu cầu client biết và thiết lập Header
- Không hiển thị version rõ ràng khi truy cập qua browser
3. Media Type Versioning – MIME type tùy biến
Accept: application/vnd.company.v1+json
✅ Ưu điểm:
- Chuẩn RESTful theo RFC
- Có thể tích hợp tốt với content negotiation
🚫 Nhược điểm:
- Phức tạp, ít người dùng
- Yêu cầu định dạng chính xác từ client
4. Custom Strategy – Tự định nghĩa version từ request
Có thể lấy version từ query param, JWT claims, subdomain, v.v.
?version=2
🧪 NestJS hỗ trợ versioning như thế nào?
Kể từ NestJS 8, bạn có thể bật versioning trong main.ts
:
app.enableVersioning({
type: VersioningType.URI, // hoặc HEADER, MEDIA_TYPE, CUSTOM
});
Các loại VersioningType:
VersioningType.URI
VersioningType.HEADER
VersioningType.MEDIA_TYPE
VersioningType.CUSTOM
🔧 Cài đặt versioning cơ bản
Step 1: Bật versioning trong main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { VersioningType } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableVersioning({
type: VersioningType.URI,
defaultVersion: '1', // Có thể là VERSION_NEUTRAL
});
await app.listen(3000);
}
bootstrap();
Step 2: Sử dụng @Version()
trong controller
import { Controller, Get, Version } from '@nestjs/common';
@Controller('products')
export class ProductController {
@Get()
@Version('1')
findAllV1() {
return { version: 'v1', data: ['Coffee', 'Tea'] };
}
@Get()
@Version('2')
findAllV2() {
return { version: 'v2', data: ['Cà phê sữa', 'Trà đào', 'Trà sữa'] };
}
}
🧬 Kiến trúc versioning nâng cao
📦 Cách tách logic theo version
Bạn có thể chia thành nhiều controller riêng biệt, mỗi controller xử lý một version cụ thể:
// product.controller.v1.ts
@Controller({ path: 'products', version: '1' })
export class ProductControllerV1 {
@Get()
getProducts() {
return ['A', 'B'];
}
}
// product.controller.v2.ts
@Controller({ path: 'products', version: '2' })
export class ProductControllerV2 {
@Get()
getProducts() {
return ['A+', 'B+', 'C'];
}
}
Điều này giúp tách biệt hoàn toàn logic, dễ bảo trì, đặc biệt với hệ thống lớn.
⚙️ Versioning với VersioningType.HEADER
app.enableVersioning({
type: VersioningType.HEADER,
header: 'X-API-Version',
});
@Version('1')
@Get()
getSomethingV1() {
return 'v1';
}
@Version('2')
@Get()
getSomethingV2() {
return 'v2';
}
Gửi request:
GET /products
X-API-Version: 2
🧭 Versioning với Media Type
app.enableVersioning({
type: VersioningType.MEDIA_TYPE,
});
Request sẽ như sau:
GET /users
Accept: application/vnd.myapp.v1+json
🎛️ Versioning nâng cao: CUSTOM
app.enableVersioning({
type: VersioningType.CUSTOM,
extractor: (request: Request) => {
return request.headers['x-custom-version'] as string;
},
});
Bạn hoàn toàn có thể lấy version từ bất cứ đâu:
- Token JWT
- Query param
- Subdomain
- Session cookie
🏗️ Kết hợp versioning với kiến trúc module hóa
Một mẹo tổ chức code hiệu quả là tách version ra thành module riêng:
src/
└── modules/
└── product/
├── v1/
│ ├── product.controller.ts
│ └── product.service.ts
└── v2/
├── product.controller.ts
└── product.service.ts
Cách này giúp:
- Dễ dàng xóa bỏ version cũ khi không còn dùng
- Có thể load module version cụ thể theo cấu hình
- Phục vụ testing riêng biệt cho từng version
🧪 Testing cho từng version
Khi có nhiều version, việc test logic từng phiên bản là rất quan trọng. Bạn nên:
- Viết unit test riêng cho từng version controller
- Mock service layer theo version
- Tạo integration test có HTTP call kèm version header/uri
Ví dụ:
describe('ProductController V2', () => {
it('should return enhanced product list', async () => {
const res = await request(app.getHttpServer())
.get('/v2/products')
.expect(200);
expect(res.body).toEqual(expect.arrayContaining(['Cà phê sữa']));
});
});
🔁 Xử lý migration và deprecation giữa các version
Khi bạn có phiên bản v2 và muốn dần loại bỏ v1, hãy cân nhắc:
- Thêm metadata thông báo deprecated:
@ApiDeprecated() // Với Swagger
@Version('1')
@Get()
getOldData() { return ... }
- Log cảnh báo từ phía server khi có truy cập v1:
@Get()
@Version('1')
getOld() {
this.logger.warn('Client using deprecated v1 API');
...
}
- Thông báo cho client thông qua docs, changelog hoặc header phản hồi:
res.setHeader('X-Deprecated', 'This version will be removed in Q3/2025');
🛡️ Kết hợp versioning với API Gateway hoặc Reverse Proxy
Nếu bạn đang sử dụng API Gateway như Kong, AWS API Gateway, hoặc Traefik, versioning có thể được quản lý ở tầng đó luôn:
- Redirect /v1/* đến service version 1
- Redirect /v2/* đến service version 2
- Có thể rate-limit theo version (vd: giới hạn request v1 để khuyến khích upgrade)
🧠 Một số best practices với Versioning
Kinh nghiệm | Mô tả |
---|---|
Luôn đặt version ngay từ đầu | Dù chưa cần dùng nhiều version, hãy chuẩn bị từ đầu. |
Không sửa đổi breaking changes trong version cũ | Hãy tạo version mới nếu cần thay đổi đáng kể. |
Viết docs rõ ràng cho từng version | Swagger, Postman Collection, Redoc... đều nên có bản riêng cho mỗi version. |
Tách controller/module theo version nếu hệ thống lớn | Tăng maintainability, tránh logic spaghetti. |
Dùng enum/constants để quản lý version | Tránh hardcode version số trong nhiều nơi. |
📦 Kết hợp với Swagger
NestJS cho phép cấu hình Swagger riêng cho từng version:
const config = new DocumentBuilder()
.setTitle('My API')
.setVersion('1.0')
.addServer('/v1') // URI versioning
.build();
Bạn cũng có thể tạo Swagger document cho từng version khác nhau.
📌 Khi nào nên tạo phiên bản mới?
Thay đổi | Tạo version mới? |
---|---|
Thêm field mới | ❌ Không cần |
Xoá hoặc đổi tên field | ✅ Nên có |
Thay đổi logic hoặc cấu trúc response | ✅ Bắt buộc |
Thay đổi minor không ảnh hưởng client | ❌ Tuỳ trường hợp |
💬 Câu hỏi thường gặp
-
Có nên version từng route không?
Có thể, nhưng không nên lạm dụng. Tốt nhất nên version theo nhóm logic (ví dụ: controller/module).
-
Làm sao biết khi nào nên bỏ version cũ?
Khi không còn traffic đáng kể từ version đó (theo dõi bằng logs, metrics), và đã thông báo trước đủ lâu.
-
Có nên version cả internal API?
Có. Vì dù internal, thay đổi logic vẫn ảnh hưởng đến các service khác.
🔚 Kết luận
API versioning là một phần cốt lõi của phát triển phần mềm bền vững. Với NestJS, bạn có nhiều lựa chọn và cấu hình linh hoạt để version API một cách rõ ràng, mở rộng và dễ maintain.
Đừng đợi đến khi hệ thống phức tạp mới nghĩ đến versioning. Hãy làm đúng ngay từ đầu.
📖 Tài liệu
Cảm ơn bạn đã đọc bài viết của mình. Mong rằng những chia sẻ vừa rồi sẽ giúp ích các bạn trong quá trình làm việc. Hẹn gặp lại ở những bài viết tiếp theo.
All rights reserved