Skip to content

当然!我们来聊聊一个当你的应用开始成长、迭代时,会遇到的一个至关重要的话题:API 版本控制 (Versioning)。这就像是为你的应用安装了一台“时光机”,让它能够在不断进化的同时,还能与过去的用户和平共处。

API 的“时光机”:从容应对变化的 NestJS 版本控制

想象你的应用是一座正在蓬勃发展的城市。

  • V1 版本 是城市的“历史老城区”。这里的道路(API 路由)和建筑(数据结构)已经建成,很多老居民(现有的移动 App 或前端应用)已经非常熟悉这里的布局,每天都在使用。
  • 随着城市发展,你决定规划一片“现代新区”(V2 版本)。这里有更宽阔的马路(新的路由)、更现代的摩天大楼(新的数据结构),甚至有些老城区的建筑在这里被彻底翻新或拆除。

问题来了:你在建设新城区的过程中,总不能把老城区的路给堵上,或者把老居民的家给拆了吧?这样做会引起巨大的混乱(所有依赖旧版 API 的应用全部崩溃)。

API 版本控制 就是城市规划中的“分区”策略。它允许你的“历史老城区 (V1)”和“现代新区 (V2)”同时存在,和平共存。老居民可以继续走他们熟悉的老路,新居民可以享受新区的便利。这让你的应用可以平滑地、无破坏性地进行升级和迭代

NestJS 提供了几种内置的、非常优雅的方式来实现 API 版本控制。

1. 第一步:启动“时光机” (在 main.ts 中开启版本控制)

无论你选择哪种版本控制策略,第一步总是在 main.ts 中开启版本控制功能。

src/main.ts

typescript
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,
  });

  await app.listen(3000);
}
bootstrap();

app.enableVersioning() 就是启动“时光机”的开关。关键在于 type 这个选项,它决定了我们如何区分不同的版本。NestJS 提供了几种主要的“时空道标”:


2. 三种主要的“时空道标”

第一站:URI 版本控制 (在 URL 里逛‘老城区’)

  • 策略: VersioningType.URI
  • 工作方式: 将版本号直接作为 URL 的一部分。这是最直观、最明确的方式。
  • 城市比喻: 就像街道地址明确包含了区域信息:GET /老城区/v1/市政厅 vs. GET /新城区/v2/市民中心

如何使用:

  1. main.ts 中设置 type: VersioningType.URI
  2. 在你的 Controller 或路由处理函数上,使用 @Version() 装饰器来标记它属于哪个版本。

src/cats/cats.controller.ts

typescript
import { Controller, Get, Version } from '@nestjs/common';

@Controller('cats')
export class CatsController {
  // 这个路由属于 V1 版本
  @Version('1')
  @Get()
  findAllV1() {
    return 'This is version 1 of all cats.';
  }

  // 这个路由属于 V2 版本,它可能返回了更丰富的数据结构
  @Version('2')
  @Get()
  findAllV2() {
    return { data: ['Cat A', 'Cat B'], version: 2 };
  }
}

如何访问:

  • 请求 GET /v1/cats -> NestJS 会自动匹配到 findAllV1() 方法,返回 "This is version 1 of all cats."
  • 请求 GET /v2/cats -> NestJS 会匹配到 findAllV2() 方法,返回一个 JSON 对象。

默认情况下,版本前缀是 v。你可以在 enableVersioning 中修改它:defaultVersion: '1', prefix: 'version/'


第二站:Header 版本控制 (凭“通行证”进入不同区域)

  • 策略: VersioningType.HEADER
  • 工作方式: URL 保持不变,但客户端需要在 HTTP 请求头 (Header) 中指定一个版本号。
  • 城市比喻: 城市的入口只有一个 (/cats),但你需要向门卫出示不同版本的“通行证”(Header),他才会引导你去往不同的区域。

如何使用:

  1. main.ts 中设置 type: VersioningType.HEADER,并指定 header 的名称:header: 'X-Api-Version'
  2. Controller 中的代码和 URI 版本控制完全一样,使用 @Version() 装饰器。

如何访问:

  • curl http://localhost:3000/cats -H "X-Api-Version: 1" -> 访问 findAllV1()
  • curl http://localhost:3000/cats -H "X-Api-Version: 2" -> 访问 findAllV2()
  • 如果不提供 X-Api-Version Header,会收到一个 404 Not Found 错误。

这种方式的好处是保持了 URL 的整洁,但对客户端的要求更高一些。


第三站:Media Type 版本控制 (根据“导游手册”的版本)

  • 策略: VersioningType.MEDIA_TYPE
  • 工作方式: 版本信息作为 Accept Header 的一部分被发送,遵循 application/json;v=1 这样的格式。
  • 城市比喻: 客户端(游客)在请求时,明确表示“我需要一份第一版的导游手册 (Accept Header)”,服务器(旅游局)就会提供第一版的信息。

如何使用:

  1. main.ts 中设置 type: VersioningType.MEDIA_TYPE,并指定 key:key: 'v='
  2. Controller 代码依然不变。

如何访问:

  • curl http://localhost:3000/cats -H "Accept: application/json;v=1" -> 访问 findAllV1()
  • curl http://localhost:3000/cats -H "Accept: application/json;v=2" -> 访问 findAllV2()

这是最符合 RESTful 理念的一种方式,但也是最复杂的,通常用于大型、非常正式的公共 API。


3. “时空旅行”的高级技巧

一个路由,多个版本

如果某个路由在 V1 和 V2 版本的行为完全一样,你不需要写两遍。

typescript
@Version(['1', '2']) // 这个路由同时服务于 V1 和 V2
@Get('legacy')
findLegacyData() {
  return 'This data is available in both v1 and v2.';
}

版本中立的路由

如果一个路由(比如健康检查 /health)不属于任何版本,或者应该对所有版本的请求都可用,你可以将它标记为“中立”。

typescript
import { VERSION_NEUTRAL } from '@nestjs/common';

@Version(VERSION_NEUTRAL)
@Get('health')
checkHealth() {
  return { status: 'ok' };
}

现在,无论你用 V1 还是 V2 的方式去请求 /health,都能得到响应。

整个 Controller 的版本控制

你可以直接在 @Controller 装饰器上标记版本,它会应用到该控制器下的所有路由。

typescript
@Controller('users')
@Version('1') // 这个控制器下的所有路由都默认是 V1
export class UsersV1Controller {
  @Get() // 自动成为 GET /v1/users
  findAll() {
    /* ... */
  }
}

总结

API 版本控制是专业后端开发者的必备技能,它能让你的应用在迭代中保持向后兼容性。

策略VersioningType客户端如何指定版本优点缺点
URI.URIGET /v1/cats最直观,易于理解和调试。可能会让 URL 变长。
Header.HEADERHeader: 'X-Api-Version: 1'保持 URL 整洁不如 URI 直观,需要客户端配合。
Media Type.MEDIA_TYPEHeader: 'Accept: ...;v=1'最符合 RESTful 规范。最复杂,对客户端要求最高。

对于大多数应用,URI 版本控制 是一个非常好的起点,因为它清晰、明确且易于实现。选择适合你项目需求的“时空道标”,然后开始放心地建设你的“现代新区”,而不用担心打扰到“历史老城区”的老居民们吧!