Claude-skill-registry api-version
Manage API versioning strategy for Hono routes in apps/api/src/v1. Use when creating new API versions or migrating endpoints between versions.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/api-version" ~/.claude/skills/majiayu000-claude-skill-registry-api-version && rm -rf "$T"
skills/data/api-version/SKILL.md- makes HTTP requests (curl)
API Versioning Skill
This skill helps you manage API versions in
apps/api/src/v1/ and prepare for future versions.
When to Use This Skill
- Creating a new API version (v2, v3, etc.)
- Deprecating old API endpoints
- Migrating endpoints between versions
- Planning breaking changes
- Maintaining backward compatibility
Current API Structure
apps/api/src/ ├── v1/ # Current API version │ ├── routes/ │ │ ├── cars.ts # Car registration endpoints │ │ ├── coe.ts # COE bidding endpoints │ │ ├── pqp.ts # PQP data endpoints │ │ └── health.ts # Health check │ └── index.ts # v1 router assembly └── index.ts # Main Hono app with versioned routes
Versioning Strategy
URL-Based Versioning
The project uses URL path versioning:
https://api.sgcarstrends.com/v1/carshttps://api.sgcarstrends.com/v1/coe- Future:
https://api.sgcarstrends.com/v2/cars
Benefits
- Clear, explicit versioning visible in URLs
- Easy to cache and monitor per version
- Clients can migrate at their own pace
- Multiple versions can coexist
Creating a New API Version
Step 1: Create Version Directory
mkdir -p apps/api/src/v2/routes
Step 2: Copy Existing Routes
Start with current v1 routes as a base:
cp -r apps/api/src/v1/routes/* apps/api/src/v2/routes/
Step 3: Create Version Router
Create
apps/api/src/v2/index.ts:
import { Hono } from "hono"; import { carsRouter } from "./routes/cars"; import { coeRouter } from "./routes/coe"; import { pqpRouter } from "./routes/pqp"; const v2 = new Hono(); // Mount routes v2.route("/cars", carsRouter); v2.route("/coe", coeRouter); v2.route("/pqp", pqpRouter); export default v2;
Step 4: Mount in Main App
Update
apps/api/src/index.ts:
import { Hono } from "hono"; import v1 from "./v1"; import v2 from "./v2"; const app = new Hono(); // Mount API versions app.route("/v1", v1); app.route("/v2", v2); // Add new version // Default to latest stable version app.route("/", v1); // Keep v1 as default or change to v2 when stable export default app;
Step 5: Implement Breaking Changes
Make necessary changes in v2 routes:
// v1 response format { "success": true, "data": [...], "count": 10 } // v2 response format (breaking change) { "data": [...], "meta": { "total": 10, "page": 1, "pageSize": 10 } }
Migration Patterns
1. Gradual Migration
Keep both versions running:
// v1/routes/cars.ts - deprecated but maintained export const carsRouter = new Hono(); carsRouter.get("/", async (c) => { // Old logic return c.json({ success: true, data: await getCars(), }); }); // v2/routes/cars.ts - new implementation export const carsRouter = new Hono(); carsRouter.get("/", async (c) => { // New logic with pagination const { page = 1, limit = 10 } = c.req.query(); const result = await getCars({ page, limit }); return c.json({ data: result.items, meta: { total: result.total, page, pageSize: limit, }, }); });
2. Feature Flag Pattern
Use feature flags to test changes:
import { Hono } from "hono"; export const carsRouter = new Hono(); carsRouter.get("/", async (c) => { const useV2Format = c.req.header("X-API-Version") === "2"; const data = await getCars(); if (useV2Format) { return c.json({ data, meta: { ... } }); } // v1 format return c.json({ success: true, data }); });
3. Deprecation Warnings
Add deprecation headers to v1:
import { Hono } from "hono"; export const carsRouter = new Hono(); // Add deprecation middleware carsRouter.use("*", async (c, next) => { await next(); c.header("X-API-Deprecation", "true"); c.header("X-API-Sunset", "2025-12-31"); c.header("Link", '<https://api.sgcarstrends.com/v2/cars>; rel="successor-version"'); }); carsRouter.get("/", async (c) => { // Existing logic });
Breaking Changes Checklist
When introducing breaking changes, consider:
- Response structure changes
- Required parameter additions
- Authentication method changes
- URL structure modifications
- HTTP method changes
- Header requirement changes
- Error format modifications
- Data type changes
Version Documentation
Document versions in OpenAPI/Swagger:
// apps/api/src/v2/openapi.ts import { OpenAPIHono } from "@hono/zod-openapi"; const app = new OpenAPIHono(); app.openapi( { method: "get", path: "/cars", summary: "Get car registrations (v2)", deprecated: false, tags: ["Cars"], responses: { 200: { description: "Success", content: { "application/json": { schema: carResponseSchema, }, }, }, }, }, async (c) => { // Handler } );
Version Sunset Process
1. Announce Deprecation
- Update documentation
- Add deprecation headers
- Notify API consumers
- Set sunset date
2. Monitor Usage
Track v1 usage metrics:
import { middleware } from "hono/middleware"; v1.use("*", async (c, next) => { // Log usage for monitoring console.log("v1 API usage:", { path: c.req.path, user: c.get("user")?.id, timestamp: new Date(), }); await next(); });
3. Provide Migration Guide
Create migration documentation:
# Migrating from v1 to v2 ## Breaking Changes ### Response Format **v1:** \`\`\`json { "success": true, "data": [...] } \`\`\` **v2:** \`\`\`json { "data": [...], "meta": { ... } } \`\`\` ### Pagination v2 includes built-in pagination: - Query params: `?page=1&limit=10` - Response includes `meta` with pagination info ## Migration Steps 1. Update base URL from `/v1` to `/v2` 2. Update response parsing to handle new format 3. Add pagination parameters if needed 4. Update error handling for new error format
4. Remove Old Version
After sunset date:
# Remove v1 directory rm -rf apps/api/src/v1 # Update main app # Remove v1 mounting from apps/api/src/index.ts
Testing Multiple Versions
Test all active versions:
# Test v1 curl https://api.sgcarstrends.com/v1/cars # Test v2 curl https://api.sgcarstrends.com/v2/cars # Run version-specific tests pnpm -F @sgcarstrends/api test -- src/v1 pnpm -F @sgcarstrends/api test -- src/v2
Deployment Considerations
Zero-Downtime Deployment
- Deploy v2 alongside v1
- Test v2 in production
- Gradually route traffic to v2
- Monitor error rates
- Rollback if issues occur
Environment Variables
Version-specific config:
# v1 settings V1_RATE_LIMIT=100 V1_CACHE_TTL=300 # v2 settings V2_RATE_LIMIT=200 V2_CACHE_TTL=600
Common Scenarios
Scenario 1: Add Required Parameter
v1: Optional parameter
carsRouter.get("/", async (c) => { const make = c.req.query("make"); // optional return c.json(await getCars({ make })); });
v2: Required parameter (breaking change)
carsRouter.get("/", async (c) => { const make = c.req.query("make"); if (!make) { return c.json({ error: "make parameter required" }, 400); } return c.json(await getCars({ make })); });
Scenario 2: Change Data Format
v1: Flat structure
{ id: 1, make: "Toyota", model: "Camry" }
v2: Nested structure (breaking change)
{ id: 1, vehicle: { make: "Toyota", model: "Camry" } }
Scenario 3: Rename Endpoint
v1:
/cars/list
v2: /cars (breaking change - URL structure)
Solution: Redirect in v1
v1.get("/cars/list", async (c) => { c.header("X-API-Deprecated", "true"); c.header("Location", "/v2/cars"); return c.redirect("/v2/cars", 301); });
References
- Hono documentation: Use Context7 for latest docs
- Related files:
- Current API versionapps/api/src/v1/
- Main app with version mountingapps/api/src/index.ts
- API service documentationapps/api/CLAUDE.md
Best Practices
- Semantic Versioning: Use v1, v2, v3 (not v1.1, v1.2)
- Backward Compatibility: Maintain old versions during migration period
- Documentation: Document all breaking changes clearly
- Communication: Announce deprecations well in advance
- Monitoring: Track usage of deprecated endpoints
- Testing: Maintain tests for all active versions
- Graceful Sunset: Provide sufficient migration time (6-12 months)
- Error Messages: Help users migrate with clear error messages