SyntaxStudy
Sign Up
REST API Backward-Compatible API Evolution
REST API Beginner 1 min read

Backward-Compatible API Evolution

The best versioning strategy is to need it as rarely as possible. Designing for extensibility from the start—using objects instead of bare arrays, keeping response envelopes consistent, and avoiding semantic overloading of fields—reduces the need for breaking changes. Robustness principle (Postel's Law): be conservative in what you send and liberal in what you accept. APIs should ignore unknown request fields (rather than rejecting them), and clients should ignore unknown response fields (rather than failing). This allows servers to add fields without breaking old clients and clients to send future fields without breaking old servers.
Example
# ----- Design choices that enable non-breaking evolution -----

# BAD: bare array — can't add metadata later without breaking
GET /products
[{ "id": 1 }, { "id": 2 }]

# GOOD: envelope — can add meta/links without breaking clients
GET /products
{ "data": [{ "id": 1 }, { "id": 2 }], "meta": { "total": 2 } }

# BAD: boolean flag that gets overloaded
{ "active": true }   # later "active" needs to mean 3 states — now a breaking change

# GOOD: use enums from the start
{ "status": "ACTIVE" }  # can add "SUSPENDED", "PENDING" without breaking old clients

# BAD: flat structure is hard to extend
{ "user_first_name": "Alice", "user_last_name": "Smith" }

# GOOD: nested object — add fields to the object without top-level pollution
{ "user": { "firstName": "Alice", "lastName": "Smith" } }

# Additive changes that are non-breaking:
# - Add new optional request fields
# - Add new response fields (clients MUST ignore unknown fields)
# - Add new enum values (clients MUST handle unknown enums gracefully)
# - Add new endpoints
# - Add new optional query parameters

# Contract testing with Pact ensures both sides honour the contract:
# npm install @pact-foundation/pact
# Generates: pacts/consumer-provider.json verified on CI