API versioning strategies — URL vs header versioning
URL versioning, header versioning, Accept header, version deprecation, breaking vs non-breaking changes, versioning tradeoffs
API Versioning Strategies
Once an API has consumers, breaking changes require a new version. The choice of versioning strategy affects discoverability, caching, and client migration friction.
URL Versioning
The most common approach. Version is part of the path: /api/v1/users. Easy to test in a browser, easy to route at the infrastructure level, clearly visible in logs.
// Mount v1 and v2 routers separately
const v1Router = require('./routes/v1');
const v2Router = require('./routes/v2');
app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router);Header Versioning
Version is passed in an HTTP header, typically Accept or a custom API-Version header. Keeps URLs clean but makes versioning invisible to casual users and harder to test without tooling.
// Middleware to route by version header
app.use('/api/users', (req, res, next) => {
const version = req.headers['api-version'] || '1';
if (version === '2') {
return v2UserController.handle(req, res, next);
}
v1UserController.handle(req, res, next);
});URL versioning is the right default for public APIs — it's explicit and easy to document. Reserve header versioning for internal APIs where you control all clients. Always document a deprecation timeline before removing old versions; give consumers at least six months.
