CodingNic

REST API Design

API Versioning

REST API Design 10 min read

API Versioning

Objectives

By the end of this lesson, you should be able to:

  • Explain why an API needs versioning at all
  • Version an API using a URL prefix and Express’s Router
  • Run two versions of the same endpoint side by side

💡 Why this matters: An API used by other developers or applications can’t just change shape overnight, a client depending on a field that gets renamed or removed breaks the moment that change ships. Versioning gives a safe path to evolve an API without breaking everyone using it.

⚠️ A note on verification: every snippet and every response shown below was actually run and tested with real HTTP requests.

Why Versioning Matters

Once an API is in use, its response shape is a contract, other code depends on specific field names, specific structures. Renaming a field, restructuring a response, or removing something a client relies on breaks that contract, versioning gives a way to make that kind of change available to new or updated clients, while existing clients keep working against the version they were built for, unaffected, until they choose to upgrade.

Versioning with a URL Prefix

javascript
const v1Router = express.Router();
v1Router.get('/products', (req, res) => {
  res.json({ version: 'v1', products: [{ id: 1, name: 'Keyboard' }] });
});
app.use('/api/v1', v1Router);

const v2Router = express.Router();
v2Router.get('/products', (req, res) => {
  res.json({ version: 'v2', data: [{ id: 1, title: 'Keyboard' }] }); // renamed field, different shape
});
app.use('/api/v2', v2Router);
bash
curl http://localhost:4503/api/v1/products
text
{"version":"v1","products":[{"id":1,"name":"Keyboard"}]}
bash
curl http://localhost:4503/api/v2/products
text
{"version":"v2","data":[{"id":1,"title":"Keyboard"}]}

express.Router() creates a mini, self-contained set of routes, mounted onto the main app with app.use(prefix, router), /api/v1 and /api/v2 are entirely separate route trees here, v2’s response deliberately renames name to title and products to data, exactly the kind of breaking change versioning exists to make safe: v1 clients keep receiving the exact shape they always have, completely unaffected by v2’s existence.

URL Prefix Versioning vs Header Versioning

A URL prefix (/api/v1/...), used above, is the most common and most discoverable approach, the version is visible right in the URL, easy to test with curl, easy to document. An alternative, header-based versioning (a client sends Accept: application/vnd.myapi.v2+json or a custom header instead of changing the URL), keeps URLs version-free but is less visible and harder to test casually, most public APIs favor the URL prefix specifically for that discoverability.

When to Bump a Version

Not every change requires a new version, adding a new, optional field to a response, or a new endpoint, doesn’t break existing clients, they simply ignore what they don’t expect. A new version is warranted specifically for breaking changes, renaming or removing a field, changing a field’s type, changing a status code’s meaning, anything an existing client’s code would need to change to keep working correctly.

Try It

  1. Build a v1 and v2 router for the same resource, with v2 deliberately changing the response shape, and confirm both versions work independently with curl.
  2. Add a new, optional field to v1’s response without bumping the version, and explain, in your own words, why this doesn’t require a new version.
  3. Add a genuinely breaking change (renaming a required field) to a v2 router, keeping v1 completely untouched, and confirm both still respond correctly.
  4. Explain, in your own words, the tradeoff between URL-prefix versioning and header-based versioning.

Recap

  • An API’s response shape is a contract, versioning lets it evolve without breaking clients depending on the current shape.
  • express.Router() plus app.use('/api/vN', router) is the standard way to run multiple API versions side by side.
  • Only breaking changes (renamed or removed fields, changed types) require a new version, additive changes don’t.

Next lesson: pagination, returning a large collection in manageable pieces.