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?