Backwards Compatible API Changes
When making a change to an API or Database Schema, by default most changes aren’t backwards compatible (safe) with previous client versions.
For example:
change | safe for API Responses? | safe for API Requests? | safe for Database Schema? |
---|---|---|---|
add required field | ✅ prev clients ignore when deserializing |
❌ prev clients won’t send it |
❌ prev client won’t create with it |
add optional field | ✅ prev clients ignore when deserializing |
✅ prev clients won’t send it |
✅ prev clients won’t create with it |
rename field | ❌ prev clients expect prev name |
❌ prev clients send prev name |
❌ prev clients fetch/create prev name |
delete field | ❌ prev clients expect field |
❌ prev clients send field |
❌ prev clients fetch/create prev field |
change field type | ❌ prev clients expect prev field type |
❌ prev clients send prev field type |
❌ prev clients fetch/create prev field type |
change null field to non-null | ✅ prev clients still deserialize |
❌ prev clients send null value |
❌ prev clients create with null |
add variant to enum/union | ❌ prev clients get error deserializing |
✅ prev clients send subset |
❌ prev clients fetch not expecting variant |
remove variant from enum/union | ✅ prev clients deserialize subset |
❌ prev clients send removed variant |
❌ prev clients send removed variant |
Making Changes Compatible
add required field
- Make field nullable
- Update all clients to send value
- Make field required
rename field
- Create new field (synced to prev field, dual writes?)
- Update all clients to use new field
- Remove prev field
delete field
- Delete from all clients
- Remove field
change field type
- Create new field with new type (and synced, assuming field type change is compatible, i.e.,
varchar
->text
) - Update all clients to use new field
- Remove prev field
change null field to non-null
Add default for the field (for null case)
or
- Update all clients to send value
- Change field nullability
add variant to enum/union
- Serialze enum/union as wider type, like a
string
instead of a union ofstring
literals ("ok" | "error" | "pending"
)
or
- Update clients to support deserializing unknown values, like io-ts’
withFallback
. Related post with further discussion, Exhaustiveness Checking
remove variant from enum/union
- Create new field (synced to prev field but w/o new variant)
- Update all clients to use new field
- Remove prev field
Related Tools
- openapi-diff check for breaking changes in Open API spec
- apollo schema checks check for breaking changes in graphql schema
- planetscale check for breaking MySQL schema changes using usage data
- squawk check for breaking changes in Postgres schema
- buf check for breaking changes in Protobuf
Conclusion
Be careful changing your API, there are some tools to make it easier!
In general, most API changes are multi-step (and can’t be made in a single PR).
- make setup change
- update all clients (takes a while)
- make desired change