API Versioning

Ship Breaking Changes Without Breaking Clients

API Versioning

Versioning lets you evolve an API while keeping older clients alive. URL path is the pragmatic choice.

3 min read Level 2/5 #express#versioning#api
What you'll learn
  • Pick a versioning style
  • Mount versioned routers
  • Plan deprecations

You will break your API. Versioning lets you do it without breaking existing clients.

URL Path — the Pragmatic Choice

import v1 from "./routes/v1/index.js";
import v2 from "./routes/v2/index.js";

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

Pros: visible in URLs, simple to test, copy-paste a curl command and the version is right there.

Cons: a touch unREST-ful (a resource has multiple URLs).

Header — the Pure Approach

Accept: application/vnd.myapi.v2+json

Cleaner in theory. In practice — debugging curl with custom Accept headers gets annoying fast, and intermediaries (CDNs, proxies) may strip them.

What Counts As Breaking

ChangeBreaking?
Add a new endpointNo
Add a new optional field to a responseNo
Add a new required field to a requestYes
Rename a response fieldYes
Change a field’s typeYes
Tighten a validation ruleYes
Remove an endpointYes
Change a status codeYes

When you’re about to make a Yes — version.

Versioned Code

Two ways:

Separate Files

src/routes/v1/users.js
src/routes/v2/users.js

Simple but doubles code. Fine for small APIs.

Shared Controllers, Versioned Adapters

// shared service
const user = await userService.find(id);

// v1 response
res.json({ user });

// v2 response (renamed wrapper, added field)
res.json({ data: { ...user, displayName: user.name } });

The service stays the same. Only the controller’s response shape varies by version.

Deprecation Plan

Don’t accumulate versions forever. The lifecycle:

  1. Ship v2 — announce the new version
  2. Keep v1 running — typically 6-12 months
  3. Send Deprecation headers on v1 responses
v1Router.use((req, res, next) => {
  res.set("deprecation", "true");
  res.set("sunset", "Thu, 31 Dec 2026 23:59:59 GMT");
  res.set("link", '</api/v2>; rel="successor-version"');
  next();
});
  1. Monitor — what’s still hitting v1?
  2. Sunset — turn off v1 once usage is near zero

The Easy Way Out

Best version of any API: one that never needs versioning. Make fields additive, never required-once-optional. Use enums + extras (“we may add new values”). Stay loose.

Error Responses →