Gợi ý Chương này chỉ liên quan đến các ứng dụng dựa trên HTTP.
Versioning cho phép bạn chạy nhiều phiên bản khác nhau của controller hoặc từng route riêng lẻ trong cùng một ứng dụng. Ứng dụng thay đổi liên tục, và việc phải giới thiệu breaking change trong khi vẫn cần hỗ trợ phiên bản cũ của ứng dụng là điều không hiếm gặp.
Ghi chú người dịch Nếu bạn từng làm API versioning ở Express hoặc Laravel, ý tưởng ở đây giống nhau: bạn vẫn phải tự định nghĩa route/controller cho từng version, Nest chỉ cung cấp cơ chế “route” request đến đúng version dựa trên URI, header, hay Accept header — nó không tự sinh ra logic tương thích ngược giúp bạn.
Có 4 loại versioning được hỗ trợ:
URI Versioning |
Phiên bản sẽ được truyền trong URI của request (mặc định) |
Header Versioning |
Một header tùy chỉnh của request sẽ chỉ định phiên bản |
Media Type Versioning |
Header Accept của request sẽ chỉ định phiên bản |
Custom Versioning |
Bất kỳ khía cạnh nào của request đều có thể dùng để chỉ định phiên bản. Một hàm tùy chỉnh được cung cấp để trích xuất phiên bản đó. |
URI Versioning Type
URI Versioning sử dụng phiên bản được truyền trong URI của request, ví dụ như https://example.com/v1/route và https://example.com/v2/route.
Chú ý Với URI Versioning, phiên bản sẽ tự động được thêm vào URI ngay sau global path prefix (nếu có), và trước bất kỳ đường dẫn controller hoặc route nào.
Để bật URI Versioning cho ứng dụng của bạn, làm như sau:
const app = await NestFactory.create(AppModule);
// hoặc "app.enableVersioning()"
app.enableVersioning({
type: VersioningType.URI,
});
await app.listen(process.env.PORT ?? 3000);
Chú ý Theo mặc định, phiên bản trong URI sẽ tự động được thêm tiền tố v, tuy nhiên giá trị tiền tố này có thể cấu hình bằng cách đặt khóa prefix thành tiền tố mong muốn, hoặc false nếu bạn muốn tắt nó.
Gợi ý Enum VersioningType có thể dùng cho thuộc tính type và được import từ package @nestjs/common.
Header Versioning Type
Header Versioning sử dụng một request header tùy chỉnh do người dùng định nghĩa, với giá trị chỉ định phiên bản sử dụng cho request đó.
Ví dụ HTTP Request cho Header Versioning:
Để bật Header Versioning cho ứng dụng của bạn, làm như sau:
const app = await NestFactory.create(AppModule);
app.enableVersioning({
type: VersioningType.HEADER,
header: 'Custom-Header',
});
await app.listen(process.env.PORT ?? 3000);
Thuộc tính header là tên của header sẽ chứa phiên bản của request.
Gợi ý Enum VersioningType có thể dùng cho thuộc tính type và được import từ package @nestjs/common.
Media Type Versioning Type
Media Type Versioning sử dụng header Accept của request để chỉ định phiên bản.
Trong header Accept, phiên bản sẽ được tách ra khỏi media type bằng dấu chấm phẩy ;. Nó sẽ chứa một cặp key-value đại diện cho phiên bản sử dụng cho request, ví dụ Accept: application/json;v=2. Key được xem như tiền tố cho phiên bản, nên thuộc tính key phải được cấu hình để bao gồm cả key và dấu phân tách.
Để bật Media Type Versioning cho ứng dụng của bạn, làm như sau:
const app = await NestFactory.create(AppModule);
app.enableVersioning({
type: VersioningType.MEDIA_TYPE,
key: 'v=',
});
await app.listen(process.env.PORT ?? 3000);
Thuộc tính key là key và dấu phân tách của cặp key-value chứa phiên bản. Với ví dụ Accept: application/json;v=2, thuộc tính key sẽ được đặt là v=.
Gợi ý Enum VersioningType có thể dùng cho thuộc tính type và được import từ package @nestjs/common.
Custom Versioning Type
Custom Versioning sử dụng bất kỳ khía cạnh nào của request để chỉ định phiên bản (hoặc các phiên bản). Request đến sẽ được phân tích
bằng một hàm extractor trả về một string hoặc một mảng string.
Nếu bên gửi request cung cấp nhiều phiên bản, hàm extractor có thể trả về một mảng string, được sắp xếp theo thứ tự từ phiên bản lớn nhất/cao nhất đến nhỏ nhất/thấp nhất. Các phiên bản sẽ được khớp với route theo thứ tự từ cao đến thấp.
Nếu extractor trả về một string hoặc mảng rỗng, không route nào được khớp và một 404 sẽ được trả về.
Ví dụ, nếu một request đến chỉ định nó hỗ trợ các phiên bản 1, 2, và 3, hàm extractor PHẢI trả về [3, 2, 1]. Điều này đảm bảo route có phiên bản cao nhất có thể sẽ được chọn trước.
Nếu các phiên bản [3, 2, 1] được trích xuất, nhưng route chỉ tồn tại cho phiên bản 2 và 1, route khớp với phiên bản 2
sẽ được chọn (phiên bản 3 sẽ tự động bị bỏ qua).
Chú ý Với Express adapter, hãy đảm bảo extractor của bạn trả về một phiên bản duy nhất (một string, hoặc một mảng chỉ có một phần tử). Việc chọn phiên bản khớp cao nhất từ một mảng nhiều phần tử không hoạt động ổn định trong Express do hạn chế thiết kế của adapter này — nếu bạn cần hành vi đó, hãy dùng Fastify adapter, vốn hỗ trợ cả chọn phiên bản đơn lẫn chọn phiên bản khớp cao nhất.
Để bật Custom Versioning cho ứng dụng của bạn, tạo một hàm extractor và truyền nó vào ứng dụng
như sau:
// Ví dụ extractor lấy ra danh sách các phiên bản từ một header tùy chỉnh và biến nó thành một mảng đã sắp xếp.
// Ví dụ này dùng Fastify, nhưng request của Express có thể được xử lý theo cách tương tự.
const extractor = (request: FastifyRequest): string | string[] =>
[request.headers['custom-versioning-field'] ?? '']
.flatMap(v => v.split(','))
.filter(v => !!v)
.sort()
.reverse()
const app = await NestFactory.create(AppModule);
app.enableVersioning({
type: VersioningType.CUSTOM,
extractor,
});
await app.listen(process.env.PORT ?? 3000);
Usage (Cách sử dụng)
Versioning cho phép bạn đánh phiên bản cho controller, từng route riêng lẻ, và cũng cung cấp cách để một số resource opt-out khỏi versioning. Cách sử dụng versioning giống nhau bất kể ứng dụng của bạn dùng Versioning Type nào.
Chú ý Nếu versioning được bật cho ứng dụng nhưng controller hoặc route không chỉ định phiên bản, mọi request đến controller/route đó sẽ nhận về response status 404. Tương tự, nếu một request được nhận với phiên bản không có controller hoặc route tương ứng, nó cũng sẽ nhận về response status 404.
Controller versions (Phiên bản controller)
Một phiên bản có thể được áp dụng cho một controller, thiết lập phiên bản cho tất cả route trong controller đó.
Để thêm phiên bản cho một controller, làm như sau:
@Controller({
version: '1',
})
export class CatsControllerV1 {
@Get('cats')
findAll(): string {
return 'This action returns all cats for version 1';
}
}
@Controller({
version: '1',
})
export class CatsControllerV1 {
@Get('cats')
findAll() {
return 'This action returns all cats for version 1';
}
}
Route versions (Phiên bản route)
Một phiên bản có thể được áp dụng cho từng route riêng lẻ. Phiên bản này sẽ override bất kỳ phiên bản khác có thể ảnh hưởng đến route, chẳng hạn Controller Version.
Để thêm phiên bản cho từng route riêng lẻ, làm như sau:
import { Controller, Get, Version } from '@nestjs/common';
@Controller()
export class CatsController {
@Version('1')
@Get('cats')
findAllV1(): string {
return 'This action returns all cats for version 1';
}
@Version('2')
@Get('cats')
findAllV2(): string {
return 'This action returns all cats for version 2';
}
}
import { Controller, Get, Version } from '@nestjs/common';
@Controller()
export class CatsController {
@Version('1')
@Get('cats')
findAllV1() {
return 'This action returns all cats for version 1';
}
@Version('2')
@Get('cats')
findAllV2() {
return 'This action returns all cats for version 2';
}
}
Multiple versions (Nhiều phiên bản)
Nhiều phiên bản có thể được áp dụng cho một controller hoặc route. Để dùng nhiều phiên bản, đặt version thành một mảng.
Để thêm nhiều phiên bản, làm như sau:
@Controller({
version: ['1', '2'],
})
export class CatsController {
@Get('cats')
findAll(): string {
return 'This action returns all cats for version 1 or 2';
}
}
@Controller({
version: ['1', '2'],
})
export class CatsController {
@Get('cats')
findAll() {
return 'This action returns all cats for version 1 or 2';
}
}
Version “Neutral” (Phiên bản “trung lập”)
Một số controller hoặc route có thể không quan tâm đến phiên bản và có chức năng như nhau bất kể phiên bản là gì. Để làm được điều này, version có thể được đặt thành symbol VERSION_NEUTRAL.
Một request đến sẽ được map vào controller hoặc route VERSION_NEUTRAL bất kể phiên bản được chỉ định là gì, kể cả khi request không chứa phiên bản nào.
Chú ý Với URI Versioning, một resource VERSION_NEUTRAL sẽ không có phiên bản xuất hiện trong URI.
Để thêm một controller hoặc route version neutral, làm như sau:
import { Controller, Get, VERSION_NEUTRAL } from '@nestjs/common';
@Controller({
version: VERSION_NEUTRAL,
})
export class CatsController {
@Get('cats')
findAll(): string {
return 'This action returns all cats regardless of version';
}
}
import { Controller, Get, VERSION_NEUTRAL } from '@nestjs/common';
@Controller({
version: VERSION_NEUTRAL,
})
export class CatsController {
@Get('cats')
findAll() {
return 'This action returns all cats regardless of version';
}
}
Global default version (Phiên bản mặc định toàn cục)
Nếu bạn không muốn cung cấp phiên bản cho từng controller hoặc route riêng lẻ, hoặc nếu bạn muốn một phiên bản cụ thể đóng vai trò mặc định cho mọi controller/route không chỉ định phiên bản, bạn có thể đặt defaultVersion như sau:
app.enableVersioning({
// ...
defaultVersion: '1'
// hoặc
defaultVersion: ['1', '2']
// hoặc
defaultVersion: VERSION_NEUTRAL
});
Middleware versioning (Đánh phiên bản cho middleware)
Middleware cũng có thể dùng metadata versioning để cấu hình middleware cho phiên bản của một route cụ thể. Để làm vậy, cung cấp số phiên bản như một trong các tham số cho phương thức MiddlewareConsumer.forRoutes():
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware.js';
import { CatsModule } from './cats/cats.module.js';
import { CatsController } from './cats/cats.controller.js';
@Module({
imports: [CatsModule],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes({ path: 'cats', method: RequestMethod.GET, version: '2' });
}
}
Với đoạn code trên, LoggerMiddleware chỉ được áp dụng cho phiên bản '2' của endpoint /cats.
Lưu ý Middleware hoạt động với mọi loại versioning được mô tả trong phần này: URI, Header, Media Type hoặc Custom.
Ghi chú người dịch Cơ chế này khá giống route grouping/prefix có version trong Laravel (Route::prefix('v1')->group(...)) hoặc namespace version trong Express router — điểm khác là Nest gắn version vào metadata của route/controller thay vì mount một router con riêng, nên bạn có thể trộn nhiều loại versioning (URI, header…) mà không cần viết lại cấu trúc route.
Ghi chú người dịch Điểm dễ gây lỗi khi vibe-code: nếu bật enableVersioning() nhưng quên gắn @Version() hoặc version cho controller, route đó sẽ trả 404 chứ không phải chạy như route “không version” — vì Nest coi thiếu version là không khớp, không phải bỏ qua kiểm tra version.