hectoday
DocsCoursesChangelog GitHub
DocsCoursesChangelog GitHub

Access Required

Enter your access code to view courses.

Invalid code

← All courses API versioning and evolution with @hectoday/http

Why versioning

  • Breaking changes
  • The versioning contract
  • Project setup

Versioning strategies

  • URL path versioning
  • Header versioning
  • Query parameter versioning
  • Choosing a strategy

Building versioned APIs

  • Side-by-side versions
  • Version routers
  • Validation per version

Evolving without breaking

  • Additive changes
  • Deprecation
  • Field renaming and removal
  • Changing response shapes

Lifecycle management

  • Sunset policies
  • Monitoring version usage
  • Checklist

Breaking changes

Your API has clients

You built an API. It works. A mobile app calls it. A frontend SPA relies on it. Maybe a partner integration reads data from it every hour. Each of these clients was written against your current API, meaning the shape of your requests and responses, the URL paths, the status codes.

Now you need to make a change. Maybe you want to rename a field. Maybe you want to restructure the response. Maybe you need to remove an endpoint entirely.

The question is: will these changes break your clients?

What actually breaks a client

A breaking change is any change that causes existing clients to fail without being updated. The client’s code expects one thing, but the API now returns something different.

Let’s look at the most common ways this happens.

Removing a field:

// What the client expects (the current response)
{ "id": "book-1", "title": "Kindred", "author_name": "Octavia Butler" }

// What you deployed (author_name removed)
{ "id": "book-1", "title": "Kindred" }

// The client's code:
const author = response.author_name; // undefined (was "Octavia Butler")
displayAuthor(author.toUpperCase()); // TypeError: Cannot read properties of undefined

The client accesses author_name. That field used to exist in every response. You removed it. Now author_name is undefined, and the client crashes the moment it tries to call .toUpperCase() on it.

Renaming a field:

// Before: author_name
{ "author_name": "Octavia Butler" }

// After: authorName (same data, different key)
{ "authorName": "Octavia Butler" }

// Client still reads author_name, gets undefined

This is a sneaky one. Renaming looks harmless because the data is still there. But to the client, renaming is the same as removing the old name and adding a new one. The client still reads author_name, and that key no longer exists.

Changing a field’s type:

// Before: rating is a number
{ "rating": 4.5 }

// After: rating is now an object
{ "rating": { "average": 4.5, "count": 12 } }

// Client code: const stars = "★".repeat(Math.round(response.rating))
// Math.round({}) returns NaN, the star display is broken

The data is richer now, which seems like an improvement. But the client expected a number. It got an object. Math.round on an object returns NaN, and the entire feature breaks silently.

Removing an endpoint:

// Client calls GET /books/popular
// You remove the endpoint
// Client gets 404, the feature is broken

Changing URL structure:

// Client calls GET /books/:id
// You change it to GET /catalog/items/:id
// Client gets 404

Changing status codes:

// Client checks for 200
if (response.status === 200) {
  /* success */
}

// You change the success response to 201
// Client thinks the request failed

What do you think happens if a client checks for a specific status code and you change it? The logic breaks even though the data is fine.

What does NOT break a client

Not every change is dangerous. Some changes are completely safe.

Adding a new field:

// Before
{ "id": "book-1", "title": "Kindred" }

// After: genre added
{ "id": "book-1", "title": "Kindred", "genre": "science-fiction" }

// Client code still works: it reads id and title, ignores genre

Well-written clients ignore fields they don’t recognize. The client reads id and title, and the new genre field just sits there, unused and harmless.

Adding a new endpoint:

// New: GET /books/recommended
// Existing clients don't call this, they're not affected

If no existing client calls the new endpoint, nobody breaks.

Adding an optional parameter:

// Before: GET /books
// After: GET /books?genre=fiction (genre is optional)
// Existing clients call GET /books without genre, still works

The genre parameter is optional. Requests without it behave exactly like before.

Making a required field optional:

// Before: { title: required, genre: required }
// After: { title: required, genre: optional }
// Clients still send genre, still valid

Clients that already send genre keep working. The only difference is that new clients can skip it.

The rule

Here’s the rule that makes all of this simple:

Additive changes are safe. Subtractive or transformative changes break clients.

Adding is always safe. New fields, new endpoints, new optional parameters. None of these affect existing clients because existing clients don’t use them.

Removing, renaming, or changing the type of anything that already exists is a breaking change. Full stop.

This is the rule we’ll build on for the rest of the course. Next, we’ll look at what happens when you make this rule explicit by turning it into a versioning contract.

Exercises

Exercise 1: List five changes to an API response that would break existing clients.

Exercise 2: List five changes that are safe and would not break existing clients.

Exercise 3: Your API returns { "created_at": "2024-01-15T10:30:00Z" }. You want to change it to { "createdAt": "2024-01-15T10:30:00Z" }. Is this a breaking change? How would you handle it?

Which of these API changes is safe for existing clients?

The versioning contract →

© 2026 hectoday. All rights reserved.