Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 101 additions & 93 deletions skills/a7-recipe-api-versioning/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@
name: a7-recipe-api-versioning
description: >-
Recipe skill for implementing API versioning strategies using API7 Enterprise Edition (API7 EE)
and the a7 CLI. Covers URI path versioning, header-based versioning, traffic splitting
for gradual migration, and enterprise version lifecycle management.
and the a7 CLI. Covers URI path versioning, header-based versioning, traffic splitting
for gradual migration, and version lifecycle management.
version: "1.0.0"
author: API7.ai Contributors
license: Apache-2.0
metadata:
category: recipe
apisix_version: ">=3.0.0"
a7_commands:
- a7 service create
- a7 route create
- a7 route update
- a7 upstream create
- a7 route list
- a7 config sync
- a7 gateway-group get
---
Expand All @@ -22,59 +23,58 @@ metadata:

## Overview

API versioning is critical for maintaining backward compatibility while evolving your services. API7 Enterprise Edition (API7 EE) provides sophisticated traffic control mechanisms to manage multiple API versions across different **Gateway Groups**.
API versioning keeps old clients working while new versions are introduced.
In the current API7 EE model, create one service per backend version and attach
routes to those services with `service_id`.

Strategies supported:
1. **URI Path Versioning**: `/v1/service`, `/v2/service` (most common)
2. **Header-Based Versioning**: `X-API-Version: 2` or `Accept` header
3. **Query Parameter Versioning**: `?v=2`
4. **Gradual Rollout (Canary/Blue-Green)**: Shift traffic between versions based on weights
5. **Version Deprecation**: Graceful retirement with redirects or custom error messages
Supported patterns:

## When to Use
1. URI path versioning: `/v1/service`, `/v2/service`.
2. Header-based versioning: `X-API-Version: 2`.
3. Gradual rollout: send a small percentage to the new version.
4. Version deprecation: redirect or return a controlled response.

- Launching a new version of an API with breaking changes.
- Managing "Legacy", "Stable", and "Beta" versions simultaneously.
- Phasing out old versions by gradually shifting traffic.
- Providing specific versions to different **Gateway Groups** or client tiers.
## Approach A: URI Path Versioning

## Approach A: URI Path Versioning (with Gateway Groups)

Each version is routed to its respective upstream, and the version prefix is stripped before reaching the backend.

### 1. Create Versioned Upstreams
### 1. Create versioned services

```bash
# Upstream for v1 in the "production" gateway group
a7 upstream create -g production -f - <<'EOF'
a7 service create -g production -f - <<'EOF'
{
"id": "upstream-v1",
"id": "service-v1",
"name": "Service V1",
"type": "roundrobin",
"nodes": { "v1-backend:8080": 1 }
"upstream": {
"type": "roundrobin",
"nodes": [
{"host": "v1-backend", "port": 8080, "weight": 1}
]
}
}
EOF

# Upstream for v2 in the "production" gateway group
a7 upstream create -g production -f - <<'EOF'
a7 service create -g production -f - <<'EOF'
{
"id": "upstream-v2",
"id": "service-v2",
"name": "Service V2",
"type": "roundrobin",
"nodes": { "v2-backend:8080": 1 }
"upstream": {
"type": "roundrobin",
"nodes": [
{"host": "v2-backend", "port": 8080, "weight": 1}
]
}
}
EOF
```

### 2. Create Routes with URI Rewriting
### 2. Create routes with URI rewriting

```bash
# Route for v1: /v1/users -> /users on backend
a7 route create -g production -f - <<'EOF'
{
"id": "route-v1",
"uri": "/v1/*",
"upstream_id": "upstream-v1",
"name": "route-v1",
"paths": ["/v1/*"],
"service_id": "service-v1",
"plugins": {
"proxy-rewrite": {
"regex_uri": ["^/v1/(.*)", "/$1"]
Expand All @@ -83,12 +83,12 @@ a7 route create -g production -f - <<'EOF'
}
EOF

# Route for v2: /v2/users -> /users on backend
a7 route create -g production -f - <<'EOF'
{
"id": "route-v2",
"uri": "/v2/*",
"upstream_id": "upstream-v2",
"name": "route-v2",
"paths": ["/v2/*"],
"service_id": "service-v2",
"plugins": {
"proxy-rewrite": {
"regex_uri": ["^/v2/(.*)", "/$1"]
Expand All @@ -100,59 +100,62 @@ EOF

## Approach B: Header-Based Versioning

A single URI serves multiple versions based on the `X-API-Version` header using the `traffic-split` plugin.
The route defaults to V1 through `service_id`. Matching requests can be routed
to V2 with `traffic-split` and an inline upstream.

```bash
a7 route create -g production -f - <<'EOF'
{
"uri": "/api/resource",
"id": "versioned-api",
"name": "versioned-api",
"paths": ["/api/resource"],
"service_id": "service-v1",
"plugins": {
"traffic-split": {
"rules": [
{
"match": [
{ "vars": [["http_x_api_version", "==", "2"]] }
{"vars": [["http_x_api_version", "==", "2"]]}
],
"weighted_upstreams": [
{
"upstream": {
"type": "roundrobin",
"nodes": { "v2-backend:8080": 1 }
"nodes": {"v2-backend:8080": 1}
},
"weight": 1
}
]
}
]
}
},
"upstream": {
"type": "roundrobin",
"nodes": { "v1-backend:8080": 1 }
}
}
EOF
```

- Header `X-API-Version: 2` routes to V2.
- Missing header or other values route to V1 (default upstream).
Requests with `X-API-Version: 2` go to V2. Other requests stay on V1.

## Approach C: Gradual Version Rollout

Shift 10% of traffic to V2 to monitor stability before a full rollout.
Shift a small percentage of V1 traffic to V2 before making V2 the default.

```bash
a7 route update route-v1 -g production -f - <<'EOF'
{
"service_id": "service-v1",
"plugins": {
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In “Gradual Version Rollout”, the route-v1 route was created earlier with the proxy-rewrite plugin to strip the /v1 prefix. This update payload sets plugins to only traffic-split, which can inadvertently drop proxy-rewrite depending on how route updates are applied. Consider including both proxy-rewrite and traffic-split in the update payload (or explicitly documenting that the update preserves existing plugins) so the rewrite behavior remains intact during rollout.

Suggested change
"plugins": {
"plugins": {
"proxy-rewrite": {
"regex_uri": ["^/v1/(.*)", "/$1"]
},

Copilot uses AI. Check for mistakes.
"proxy-rewrite": {
"regex_uri": ["^/v1/(.*)", "/$1"]
},
"traffic-split": {
"rules": [
{
"weighted_upstreams": [
{
"upstream": {
"type": "roundrobin",
"nodes": { "v2-backend:8080": 1 }
"nodes": {"v2-backend:8080": 1}
},
"weight": 1
},
Expand All @@ -167,16 +170,16 @@ a7 route update route-v1 -g production -f - <<'EOF'
}
EOF
```
*Note: A `weighted_upstreams` entry without an `upstream` field refers back to the route's default upstream (V1).*

## Version Deprecation with Redirect
The entry without `upstream` falls back to the route's default service.

When V1 is deprecated, redirect clients to the V2 equivalent with a `301` status code.
## Version Deprecation with Redirect

```bash
a7 route update route-v1 -g production -f - <<'EOF'
{
"uri": "/v1/*",
"paths": ["/v1/*"],
"service_id": "service-v1",
"plugins": {
"redirect": {
"regex_uri": ["^/v1/(.*)", "/v2/$1"],
Expand All @@ -187,59 +190,64 @@ a7 route update route-v1 -g production -f - <<'EOF'
EOF
```

## Declarative Versioning with Config Sync

Manage the lifecycle of versions across Gateway Groups in a single YAML file.
## Config Sync

```yaml
# versioning-config.yaml
gateway_groups:
- id: production
upstreams:
- id: v1-backend
nodes: { "v1-svc:80": 1 }
- id: v2-backend
nodes: { "v2-svc:80": 1 }
routes:
- id: service-v1
uri: "/v1/*"
upstream_id: v1-backend
plugins:
proxy-rewrite:
regex_uri: ["^/v1/(.*)", "/$1"]
- id: service-v2
uri: "/v2/*"
upstream_id: v2-backend
plugins:
proxy-rewrite:
regex_uri: ["^/v2/(.*)", "/$1"]
version: "1"
services:
- id: service-v1
name: Service V1
upstream:
type: roundrobin
nodes:
- host: v1-svc
port: 80
weight: 1
- id: service-v2
name: Service V2
upstream:
type: roundrobin
nodes:
- host: v2-svc
port: 80
weight: 1
routes:
- id: service-v1-route
name: service-v1-route
paths:
- /v1/*
service_id: service-v1
plugins:
proxy-rewrite:
regex_uri: ["^/v1/(.*)", "/$1"]
- id: service-v2-route
name: service-v2-route
paths:
- /v2/*
service_id: service-v2
plugins:
proxy-rewrite:
regex_uri: ["^/v2/(.*)", "/$1"]
```

Apply the configuration:

```bash
a7 config sync -g production -f versioning-config.yaml
```
Comment thread
coderabbitai[bot] marked this conversation as resolved.

## Important Considerations

- **Gateway Group Scoping**: Always ensure your versioned routes are applied to the correct `--gateway-group`.
- **Admin API Port**: Use port `7443` (HTTPS) for API7 EE communication.
- **Authentication**: Use the `--token` flag for CLI authentication.
- **Priority**: When using multiple versioning strategies on the same URI, the order of rules in `traffic-split` is critical.
- **Regex Syntax**: `regex_uri` follows PCRE-compatible Lua regex. Double-escape special characters in JSON if necessary.

## Verification

```bash
# Verify V1 path
curl -i https://gateway.prod.example.com/v1/health

# Verify V2 path
curl -i https://gateway.prod.example.com/v2/health

# Verify Header-based versioning
curl -i -H "X-API-Version: 2" https://gateway.prod.example.com/api/resource

# Check route status in Gateway Group
a7 route list -g production
a7 route list -g production --service-id service-v1
```

## Important Considerations

- Always apply services and routes to the intended `--gateway-group`.
- Use route priorities when multiple versioning strategies overlap.
- `regex_uri` follows APISIX/Lua-compatible regex behavior.
- Prefer `service_id` for default routing and reserve inline upstreams for plugin-specific overrides.
Loading
Loading