API versioning allows you to evolve your API while maintaining backward compatibility for existing clients. When you need to make breaking changes, versioning lets old clients continue working while new clients use updated functionality.
API Versioning
Strategies for managing breaking changes in APIs while maintaining backward compatibility.
Why Version Your API
Breaking changes happen: Renaming fields, changing data types, removing endpoints - these break existing clients.
Clients can't update instantly: Mobile apps need app store approval. Third-party integrations need development time.
Versioning provides:
- Stability for existing clients
- Freedom to improve the API
- Clear migration path
- Time for clients to upgrade
What's a Breaking Change?
| Breaking | Non-Breaking |
|---|---|
| Removing a field | Adding a new field |
| Renaming a field | Adding a new endpoint |
| Changing field type | Adding optional parameter |
| Removing an endpoint | Deprecating with warning |
| Changing error format | Adding new error codes |
| Changing authentication | Extending response data |
Rule: If existing clients will fail without code changes, it's breaking.
Versioning Strategies
URL Path Versioning:
/v1/users, /v2/users
Most common. Easy to understand, route, and cache. Clear which version you're using.
Query Parameter:
/users?version=1
Keeps URLs clean. Harder to route and cache. Can be forgotten easily.
Header Versioning:
Accept: application/vnd.api+json;version=1
Clean URLs. Version hidden in headers. More complex to test and debug.
Content Negotiation:
Accept: application/vnd.company.v1+json
REST purist approach. Complex to implement and document.
Choosing a Strategy
| Strategy | Pros | Cons |
|---|---|---|
| URL Path | Simple, visible, cacheable | URL changes between versions |
| Query Param | Clean URLs | Easy to forget, caching issues |
| Header | Clean URLs, flexible | Hidden, harder to test |
Recommendation: Use URL path versioning unless you have specific reasons not to. It's the most widely used and understood approach.
Version Lifecycle
1. Active: Current recommended version. Full support.
2. Deprecated: Still works but scheduled for removal. Log warnings, notify users.
3. Sunset: End of life date announced. Return sunset headers.
4. Removed: Returns 410 Gone or redirects to docs.
Best Practices
Version at API level, not endpoint: All v1 endpoints should behave consistently. Don't version individual endpoints differently.
Document migration guides: When releasing v2, provide clear guide for upgrading from v1.
Set deprecation timeline: "v1 deprecated January 2024, sunset April 2024" - give clients time to migrate.
Use sunset headers:
Sunset: Sat, 01 Apr 2024 00:00:00 GMT
Clients can automate migration warnings.
Monitor version usage: Track which clients use which versions. Contact heavy v1 users before sunsetting.
Don't over-version: Every version is maintenance burden. Make breaking changes rare and bundle them.
Avoiding Breaking Changes
Additive changes only: Add new fields, don't remove or rename existing ones.
Use nullable fields: New optional fields with defaults don't break existing clients.
Expand enums carefully: Adding enum values can break strict client validation.
Deprecate before removing: Mark fields deprecated in v1 before removing in v2.
Common Mistakes
1. Too many versions: Supporting v1-v10 is expensive. Limit active versions to 2-3.
2. No deprecation period: Killing a version immediately breaks clients. Give 6-12 months notice.
3. Inconsistent versioning: Some endpoints versioned, some not. Version everything consistently.
4. Breaking changes in minor versions: v1.1 should be backward compatible with v1.0.
5. No version in initial release: Start with /v1 from day one. Adding versioning later is harder.
Semantic Versioning for APIs
Major (v1 → v2): Breaking changes. Clients must update code.
Minor (v1.0 → v1.1): New features, backward compatible.
Patch (v1.0.0 → v1.0.1): Bug fixes, no API changes.
Use major version in URL (/v1, /v2), document minor/patch in changelog.
Code Examples
API Versioning Implementation
// Express router with versioned routes
import { Router } from 'express';
// Version-specific routers
const v1Router = Router();
const v2Router = Router();
// V1: Original user response
v1Router.get('/users/:id', async (req, res) => {
const user = await db.users.findById(req.params.id);
res.json({
id: user.id,
name: user.fullName, // Old field name
email: user.email
});
});
// V2: Updated user response
v2Router.get('/users/:id', async (req, res) => {
const user = await db.users.findById(req.params.id);
res.json({
id: user.id,
firstName: user.firstName, // Split name field
lastName: user.lastName,
email: user.email,
createdAt: user.createdAt // New field
});
});
// Mount versioned routers
app.use('/v1', v1Router);
app.use('/v2', v2Router);
// Deprecation middleware for v1
app.use('/v1', (req, res, next) => {
res.set('Deprecation', 'true');
res.set('Sunset', 'Sat, 01 Apr 2025 00:00:00 GMT');
res.set('Link', '</v2>; rel="successor-version"');
next();
});Related Terms
N+1 Problem
A performance anti-pattern where an application makes N additional queries for N items.
OpenAPI Specification
A standard format for describing REST APIs, enabling documentation and code generation.
Mock API
A simulated API endpoint that returns predefined responses for testing and development.
API Testing
The process of verifying that APIs work correctly, securely, and perform well.