DEV Community

myougaTheAxo
myougaTheAxo

Posted on

API Versioning with Claude Code: Evolving APIs Without Breaking Clients

Every API change risks breaking existing clients. Versioning strategy, designed upfront, lets you evolve the API safely. Claude Code generates the complete versioning infrastructure when you define the rules.


CLAUDE.md for API Versioning

## API Versioning Rules

### Versioning approach
- URL path versioning: /api/v1/users, /api/v2/users
  (Simple, cacheable, easy to debug)
- Header versioning: not used

### Version management
- New version: add new files, never modify existing version
- Deprecation: notify via Deprecation-Date header (6 months before sunset)
- Removal: minimum 6 months deprecation notice required

### Backward compatibility (within same version)
- Never remove fields (make optional first, then remove in next version)
- Never rename fields (add new name, deprecate old one)
- Never change field types (string → number is breaking)
- Additions are OK (new fields, new endpoints)

### Support lifecycle
- Current: full support
- Previous: 12 months (bug fixes only)
- Older: unsupported (migration guide provided)
Enter fullscreen mode Exit fullscreen mode

Generating the Versioned Router

Generate an API versioning structure for Express.

Requirements:
- URL path versioning: /api/v1/* and /api/v2/*
- Separate routers per version
- v1 shares v2 implementation (only overrides what changed)
- v1 responses automatically include Deprecation headers
- Version info endpoint: GET /api/versions

Directory structure:
src/routes/
  v1/users.ts, v1/orders.ts, v1/index.ts
  v2/users.ts (only changed endpoints)
  v2/index.ts
  router.ts
Enter fullscreen mode Exit fullscreen mode

Generated:

// src/routes/router.ts
import { Router, Request, Response, NextFunction } from 'express';

export const apiRouter = Router();

// Attach deprecation headers to all v1 responses
const v1DeprecationMiddleware = (_req: Request, res: Response, next: NextFunction) => {
  res.setHeader('Deprecation', 'true');
  res.setHeader('Deprecation-Date', '2027-01-01');
  res.setHeader('Sunset', 'Sun, 01 Jan 2027 00:00:00 GMT');
  res.setHeader(
    'Link',
    '<https://api.example.com/api/v2>; rel="successor-version"'
  );
  next();
};

apiRouter.use('/v1', v1DeprecationMiddleware, v1Router);
apiRouter.use('/v2', v2Router);

apiRouter.get('/versions', (_req, res) => {
  res.json({
    current: 'v2',
    supported: ['v1', 'v2'],
    deprecated: [{ version: 'v1', sunset: '2027-01-01' }],
  });
});
Enter fullscreen mode Exit fullscreen mode

Designing v1 → v2 Migration

Design a backward-compatible API change.

Change:
- v1: GET /users  { id, name, email, created_at }
- v2 change: split name into firstName + lastName, snake_case  camelCase

Requirements:
- v1 unchanged (existing clients keep working)
- v2 provides new format
- Share business logic (DRY)
- Add migration notes in JSDoc

Generate:
- src/routes/v1/users.ts (existing format)
- src/routes/v2/users.ts (new format)
- src/services/userService.ts (shared logic)
Enter fullscreen mode Exit fullscreen mode

Generated:

// src/services/userService.ts — internal representation (shared)
interface UserRecord {
  id: string;
  firstName: string;
  lastName: string;
  email: string;
  createdAt: Date;
}

// src/routes/v1/users.ts — v1 format (snake_case, joined name)
router.get('/users/:id', async (req, res) => {
  const user = await userService.findById(req.params.id);
  res.json({
    id: user.id,
    name: `${user.firstName} ${user.lastName}`, // v1: joined
    email: user.email,
    created_at: user.createdAt.toISOString(),  // v1: snake_case
  });
});

// src/routes/v2/users.ts — v2 format (camelCase, split name)
/**
 * Changes from v1:
 * - `name` (string) → `firstName` + `lastName` (separate fields)
 * - `created_at` (snake_case) → `createdAt` (camelCase)
 *
 * @see https://api.example.com/docs/migration/v1-to-v2
 */
router.get('/users/:id', async (req, res) => {
  const user = await userService.findById(req.params.id);
  res.json({
    id: user.id,
    firstName: user.firstName,  // v2: split
    lastName: user.lastName,
    email: user.email,
    createdAt: user.createdAt.toISOString(), // v2: camelCase
  });
});
Enter fullscreen mode Exit fullscreen mode

Hook: Warn on v1 Modifications

# .claude/hooks/check_api_version.py
import json, sys

data = json.load(sys.stdin)
content = data.get("tool_input", {}).get("content", "") or ""
fp = data.get("tool_input", {}).get("file_path", "")

if not fp or "routes/v1" not in fp:
    sys.exit(0)

route_methods = ["router.get(", "router.post(", "router.put(", "router.delete(", "router.patch("]
if any(m in content for m in route_methods):
    print("[API] Modifying v1 routes.", file=sys.stderr)
    print("[API] v1 is deprecated. Add changes to v2 instead.", file=sys.stderr)
    sys.exit(1)  # Warning (non-blocking)

sys.exit(0)
Enter fullscreen mode Exit fullscreen mode

Summary

Design API versioning with Claude Code:

  1. CLAUDE.md — Versioning approach, compatibility rules, lifecycle
  2. URL path versioning — Clear version separation, cacheable
  3. Deprecation headers — Inform clients about upcoming changes
  4. Shared service layer — v1 and v2 differ only in response format

Code Review Pack (¥980) includes /code-review for API design review — backward compatibility violations, missing deprecation headers, inconsistent versioning.

👉 prompt-works.jp

Myouga (@myougatheaxo) — Claude Code engineer focused on API design.

Top comments (0)