Skip to content
Plurafy

API Versioning

API versioning enables backward compatibility while allowing improvements. Learn strategies for versioning RESTful and GraphQL APIs.

Implement API versioning that maintains backward compatibility, enables feature development, and provides smooth migration paths for clients.

URL Path: /api/v1/users - Most common Query Parameter: /api/users?version=1 - Simpler but less RESTful Header: API-Version: 1 - Clean but less discoverable Subdomain: v1.api.example.com - Clean but complex routing Content Negotiation: Accept: application/vnd.company.v1+json - Most RESTful

  • Backward Compatibility: Maintain old versions
  • Clear Deprecation: Announce version EOL
  • Migration Path: Help clients upgrade
  • Documentation: Clear changelog and guides
const express = require('express');
const app = express();
// API routes separated by version
app.use('/api/v1', require('./routes/v1'));
app.use('/api/v2', require('./routes/v2'));
app.use('/api/v3', require('./routes/v3'));
// Redirect old versions with sunset warning
app.use('/api/v1/*', (req, res) => {
res.setHeader('Sunset', new Date(Date.now() + 90 * 24 * 60 * 60 * 1000).toUTCString());
res.setHeader('Deprecated', 'true');
// Forward to v1 routes...
});
// routes/v1/users.js
const express = require('express');
const router = express.Router();
router.get('/:id', (req, res) => {
res.json({
id: req.params.id,
name: 'John Doe',
email: 'john@example.com',
});
});
module.exports = router;
// routes/v2/users.js
const express = require('express');
const router = express.Router();
router.get('/:id', (req, res) => {
res.json({
id: req.params.id,
firstName: 'John',
lastName: 'Doe',
email: 'john@example.com',
createdAt: new Date(),
});
});
module.exports = router;
const express = require('express');
const app = express();
// Middleware to extract version
app.use((req, res, next) => {
// API-Version header or accept version
const version = req.headers['api-version'] ||
req.query.version ||
'v1'; // default
req.apiVersion = version;
next();
});
// Route handler for multiple versions
app.get('/users/:id', (req, res) => {
const { apiVersion } = req;
if (apiVersion === 'v1') {
return res.json({
id: req.params.id,
name: 'John Doe',
email: 'john@example.com',
});
}
if (apiVersion === 'v2') {
return res.json({
id: req.params.id,
firstName: 'John',
lastName: 'Doe',
email: 'john@example.com',
createdAt: new Date(),
updatedAt: new Date(),
});
}
if (apiVersion === 'v3') {
return res.json({
data: {
id: req.params.id,
firstName: 'John',
lastName: 'Doe',
email: 'john@example.com',
},
meta: {
createdAt: new Date(),
updatedAt: new Date(),
},
});
}
res.status(400).json({ error: 'Unsupported API version' });
});
class VersionedAPI {
constructor() {
this.handlers = new Map();
this.deprecations = new Map();
}
registerHandler(path, method, version, handler) {
const key = `${method}:${path}`;
if (!this.handlers.has(key)) {
this.handlers.set(key, new Map());
}
this.handlers.get(key).set(version, handler);
}
registerDeprecation(version, message, sunset) {
this.deprecations.set(version, { message, sunset });
}
getHandler(path, method, version) {
const key = `${method}:${path}`;
const versionHandlers = this.handlers.get(key);
if (!versionHandlers) {
return null;
}
// Try exact version match
if (versionHandlers.has(version)) {
return versionHandlers.get(version);
}
// Try latest compatible version
const versions = Array.from(versionHandlers.keys())
.filter(v => v <= version)
.sort((a, b) => b - a);
return versions.length > 0 ? versionHandlers.get(versions[0]) : null;
}
middleware() {
return (req, res, next) => {
const version = parseInt(req.headers['api-version'] || '1');
const handler = this.getHandler(req.path, req.method, version);
if (!handler) {
return res.status(404).json({ error: 'Endpoint not found' });
}
// Add deprecation headers
if (this.deprecations.has(version)) {
const { message, sunset } = this.deprecations.get(version);
res.setHeader('Deprecated', 'true');
res.setHeader('Deprecation', 'true');
res.setHeader('Sunset', sunset.toUTCString());
res.setHeader('Warning', `299 - "${message}"`);
}
handler(req, res, next);
};
}
}
// Usage
const api = new VersionedAPI();
api.registerHandler('/users/:id', 'GET', 1, (req, res) => {
res.json({
id: req.params.id,
name: 'John Doe',
});
});
api.registerHandler('/users/:id', 'GET', 2, (req, res) => {
res.json({
id: req.params.id,
firstName: 'John',
lastName: 'Doe',
createdAt: new Date(),
});
});
api.registerDeprecation(1, 'Version 1 is deprecated, upgrade to v2', new Date('2024-12-31'));
app.use(api.middleware());
class APIVersion {
constructor(version) {
const [major, minor, patch] = version.split('.').map(Number);
this.major = major;
this.minor = minor;
this.patch = patch;
}
isCompatible(requiredVersion) {
const required = new APIVersion(requiredVersion);
// Major version must match
if (this.major !== required.major) {
return false;
}
// Minor version must be >= required
if (this.minor < required.minor) {
return false;
}
// If minor versions match, patch must be >=
if (this.minor === required.minor && this.patch < required.patch) {
return false;
}
return true;
}
toString() {
return `${this.major}.${this.minor}.${this.patch}`;
}
}
// Feature compatibility checking
const FEATURE_VERSIONS = {
'user-profile': '2.0.0',
'batch-operations': '2.1.0',
'webhooks': '3.0.0',
};
function requiresFeature(feature, clientVersion) {
const required = FEATURE_VERSIONS[feature];
const version = new APIVersion(clientVersion);
return version.isCompatible(required);
}
// Usage
const clientVersion = '2.1.5';
if (requiresFeature('webhooks', clientVersion)) {
// Enable webhooks
} else {
res.status(402).json({
error: 'Feature requires API version 3.0.0 or higher'
});
}
const { buildSchema, graphql } = require('graphql');
// Schema evolution with directives
const schema = buildSchema(`
directive @deprecated(reason: String) on FIELD_DEFINITION
type User {
id: ID!
name: String!
email: String!
# Deprecated, use firstName and lastName instead
fullName: String @deprecated(reason: "Use firstName and lastName")
firstName: String
lastName: String
}
type Query {
user(id: ID!): User
users: [User!]!
# Deprecated, use allUsers
listUsers: [User!]! @deprecated(reason: "Use users instead")
}
`);
// Resolver implementation
const resolvers = {
Query: {
user: (args) => fetchUser(args.id),
users: () => fetchAllUsers(),
listUsers: () => fetchAllUsers(), // Backward compat
},
User: {
fullName: (user) => `${user.firstName} ${user.lastName}`,
},
};
// Check for deprecated fields in query
function validateQuery(query) {
const deprecatedPattern = /@deprecated/;
const introspectionQuery = `
query {
__type(name: "Query") {
fields {
name
isDeprecated
deprecationReason
}
}
}
`;
return graphql(schema, introspectionQuery);
}
// Migration helper for clients
class APIClient {
constructor(baseURL, version = '3') {
this.baseURL = baseURL;
this.version = version;
}
async get(endpoint) {
const response = await fetch(`${this.baseURL}/api/v${this.version}${endpoint}`, {
headers: {
'API-Version': this.version,
},
});
if (response.headers.has('Deprecation')) {
console.warn('API version deprecated:', response.headers.get('Warning'));
console.warn('Sunset:', response.headers.get('Sunset'));
}
return response.json();
}
async migrateFromV2() {
// Help users migrate from v2 to v3
console.log('Migrating from API v2 to v3');
// Example: v2 returns 'name', v3 returns 'firstName' and 'lastName'
const oldData = await this.fetchV2Data();
return {
firstName: oldData.name.split(' ')[0],
lastName: oldData.name.split(' ')[1],
};
}
}
  1. Version Planning

    • Plan major versions carefully
    • Provide clear upgrade paths
    • Maintain compatibility windows
  2. Communication

    • Use HTTP headers for version info
    • Document all changes
    • Provide migration guides
    • Announce deprecations early
  3. Support Timeline

    • Define support periods
    • Provide sunset dates
    • Monitor version usage
    • Gradually retire old versions
  4. Testing

    • Test version compatibility
    • Test migration paths
    • Verify backward compatibility
    • Test deprecated features
  5. Monitoring

    • Track version usage
    • Monitor deprecation adoption
    • Alert on version mismatches
    • Log version in requests
  • Public APIs: Multiple version support
  • Mobile Apps: Manage app version compatibility
  • SaaS Platforms: Gradual feature rollout
  • Microservices: Independent service versioning
  • Third-party Integrations: Smooth API updates