Claude Code Plugins

Community-maintained marketplace

Feedback

Manage API versioning strategy for Hono routes in apps/api/src/v1. Use when creating new API versions or migrating endpoints between versions.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name api-version
description Manage API versioning strategy for Hono routes in apps/api/src/v1. Use when creating new API versions or migrating endpoints between versions.
allowed-tools Read, Edit, Write, Grep, Glob

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/cars
  • https://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

  1. Deploy v2 alongside v1
  2. Test v2 in production
  3. Gradually route traffic to v2
  4. Monitor error rates
  5. 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:
    • apps/api/src/v1/ - Current API version
    • apps/api/src/index.ts - Main app with version mounting
    • apps/api/CLAUDE.md - API service documentation

Best Practices

  1. Semantic Versioning: Use v1, v2, v3 (not v1.1, v1.2)
  2. Backward Compatibility: Maintain old versions during migration period
  3. Documentation: Document all breaking changes clearly
  4. Communication: Announce deprecations well in advance
  5. Monitoring: Track usage of deprecated endpoints
  6. Testing: Maintain tests for all active versions
  7. Graceful Sunset: Provide sufficient migration time (6-12 months)
  8. Error Messages: Help users migrate with clear error messages