As your API evolves, you'll need to make changes that would break existing clients if deployed carelessly. API versioning lets you introduce changes while keeping older integrations working.
Why Versioning Matters
Once an API is public (or even used by your own mobile app that users haven't all updated yet), you can't assume every client will immediately adopt every change — breaking changes without versioning breaks real users' applications without warning.
What Counts as a Breaking Change
- Removing a field from a response
- Renaming a field
- Changing a field's data type
- Removing an endpoint entirely
- Changing required request parameters
What's Generally NOT Breaking (Safe to Add Anytime)
- Adding a new optional field to a response
- Adding a new endpoint
- Adding a new optional request parameter
Versioning Strategy 1 — URL Path Versioning (Most Common)
https://api.yourdomain.com/v1/users
https://api.yourdomain.com/v2/users
Simple, visible, easy to route in Nginx or your application router:
location /v1/ {
proxy_pass http://127.0.0.1:3001;
}
location /v2/ {
proxy_pass http://127.0.0.1:3002;
}
Versioning Strategy 2 — Header-Based Versioning
GET /users HTTP/1.1
Accept: application/vnd.yourapi.v2+json
Keeps URLs clean but is less discoverable/testable than URL-based versioning (can't just paste a URL into a browser).
Versioning Strategy 3 — Query Parameter Versioning
https://api.yourdomain.com/users?version=2
Simple but less commonly used than the other two approaches; can be easily overlooked or omitted by clients.
Running Multiple Versions Simultaneously
Deploy each major version as a genuinely separate running application/container, or use routing logic within a single application to serve different response shapes based on the requested version:
app.get('/v1/users/:id', getUserV1Handler);
app.get('/v2/users/:id', getUserV2Handler);
Deprecation Process
- Announce the new version, encourage migration
- Add a deprecation warning header to old-version responses
- Set and communicate a clear sunset date
- Monitor actual usage of the old version to gauge migration progress
- Remove the old version only after usage drops to an acceptable level, or the announced sunset date passes
Adding a Deprecation Header
app.use('/v1', (req, res, next) => {
res.set('Deprecation', 'true');
res.set('Sunset', 'Sat, 31 Dec 2026 23:59:59 GMT');
next();
});
Semantic Versioning for APIs
Consider adopting a clear internal convention: major version bump for breaking changes (v1 → v2), while minor additions (new optional fields, new endpoints) don't require a version bump at all, since they're non-breaking by definition.
Documenting Version Differences
Maintain clear changelog/migration documentation for each version transition — what changed, why, and how to update client code accordingly.
Common Mistakes
- Making a breaking change without bumping the version at all
- Maintaining too many old versions indefinitely, increasing maintenance burden
- Not communicating deprecation timelines clearly to API consumers
FAQ
Do I need versioning from day one?
For an internal API with a single, fully-controlled client, it's less critical initially; for any public or third-party-consumed API, versioning from the start avoids painful retrofitting later.
Continue Reading
- How to Build and Secure a REST API on a VPS
- How to Implement Idempotent API Endpoints
- How to Choose Between REST, GraphQL, and gRPC
Browse more articles in Object Storage, Messaging & APIs.
