Advanced

Designing and Implementing API Versioning Strategies with Claude Code

Learn about designing and implementing API versioning strategies using Claude Code. Practical tips and code examples included.

Why API Versioning Matters

Breaking changes to APIs can have a large impact on client applications. With Claude Code, you can design and implement a versioning strategy end-to-end.

Three Versioning Approaches

ApproachExampleProsCons
URL path/api/v1/usersClear and easy to understandThe URL changes
HeaderAPI-Version: 1Keeps URLs cleanHarder to discover
AcceptAccept: application/vnd.app.v1+jsonFollows HTTP standardsTends to get complex

Implementing the URL Path Approach

This is the most common and straightforward approach.

import express from "express";

const app = express();

// Router per version
import v1Router from "./routes/v1";
import v2Router from "./routes/v2";

app.use("/api/v1", v1Router);
app.use("/api/v2", v2Router);

// routes/v1/users.ts
const v1Router = express.Router();

v1Router.get("/users", async (req, res) => {
  const users = await prisma.user.findMany();
  // v1 response shape
  res.json({
    data: users.map((u) => ({
      id: u.id,
      name: u.name,
      email: u.email,
    })),
  });
});

// routes/v2/users.ts
const v2Router = express.Router();

v2Router.get("/users", async (req, res) => {
  const users = await prisma.user.findMany({
    include: { profile: true },
  });
  // v2 response shape (adds pagination)
  res.json({
    data: users.map((u) => ({
      id: u.id,
      fullName: u.name,
      email: u.email,
      profile: u.profile,
    })),
    pagination: {
      total: users.length,
      page: 1,
      perPage: 20,
    },
  });
});

Implementing the Header Approach

function versionMiddleware(
  req: express.Request,
  res: express.Response,
  next: express.NextFunction
) {
  const version = req.headers["api-version"] || "1";
  req.apiVersion = parseInt(version as string, 10);

  // Check whether the version is supported
  const supportedVersions = [1, 2, 3];
  if (!supportedVersions.includes(req.apiVersion)) {
    return res.status(400).json({
      error: `Unsupported API version: ${version}`,
      supportedVersions,
    });
  }

  // Deprecation warning headers for older versions
  if (req.apiVersion < 2) {
    res.set("Deprecation", "true");
    res.set("Sunset", "2026-06-01");
    res.set(
      "Link",
      '<https://api.example.com/docs/migration>; rel="deprecation"'
    );
  }

  next();
}

app.use("/api", versionMiddleware);

app.get("/api/users", async (req, res) => {
  switch (req.apiVersion) {
    case 1:
      return handleUsersV1(req, res);
    case 2:
      return handleUsersV2(req, res);
    default:
      return handleUsersV2(req, res);
  }
});

Response Transformation Pattern

A design that absorbs differences between versions in a transformation layer.

type UserV1 = {
  id: string;
  name: string;
  email: string;
};

type UserV2 = {
  id: string;
  fullName: string;
  emailAddress: string;
  createdAt: string;
};

const transformers = {
  1: (user: DBUser): UserV1 => ({
    id: user.id,
    name: user.name,
    email: user.email,
  }),
  2: (user: DBUser): UserV2 => ({
    id: user.id,
    fullName: user.name,
    emailAddress: user.email,
    createdAt: user.createdAt.toISOString(),
  }),
};

function createVersionedHandler<T>(
  fetcher: (req: express.Request) => Promise<T[]>,
  transformerMap: Record<number, (item: T) => unknown>
) {
  return async (req: express.Request, res: express.Response) => {
    const data = await fetcher(req);
    const transform = transformerMap[req.apiVersion];
    res.json({ data: data.map(transform) });
  };
}

Deprecation and Migration Support

function deprecationNotice(
  sunsetDate: string,
  migrationGuide: string
) {
  return (
    req: express.Request,
    res: express.Response,
    next: express.NextFunction
  ) => {
    res.set("Deprecation", "true");
    res.set("Sunset", sunsetDate);
    res.set("Link", `<${migrationGuide}>; rel="deprecation"`);

    console.warn(
      `Deprecated API v${req.apiVersion} accessed: ${req.path}`
    );

    next();
  };
}

// v1 is scheduled to be retired in June 2026
app.use(
  "/api/v1",
  deprecationNotice(
    "2026-06-01",
    "https://docs.example.com/migration/v1-to-v2"
  ),
  v1Router
);

Implementation Prompts for Claude Code

Here is an example prompt for asking Claude Code to introduce API versioning. For the basics of API design, see the Claude Code getting started guide, and for error handling see error boundary design.

Introduce versioning into the existing REST API.
- Use URL path versioning (/api/v1, /api/v2)
- v1 should keep the existing API as-is
- v2 should unify the response format (add pagination)
- Also set deprecation headers on v1
- Generate migration guide documentation

For API versioning best practices, the Microsoft REST API Guidelines are a good reference. For more on using Claude Code, check the official documentation.

Summary

API versioning is an essential design concern for long-lived services. By having Claude Code understand the structure of your existing code before introducing versioning, you can evolve your API with minimal impact on existing clients.

#Claude Code #API design #versioning #REST API #TypeScript

Level up your Claude Code workflow

50 battle-tested prompt templates you can copy-paste into Claude Code right now.

Free

Free PDF: Claude Code Cheatsheet in 5 Minutes

Key commands, shortcuts, and prompt examples on a single printable page.

Download PDF
M

About the Author

Masa

Engineer obsessed with Claude Code. Runs claudecode-lab.com, a 10-language tech media with 2,000+ pages.