API Versioning & Changes
Introduction
This guide outlines our approach to API versioning, managing changes, and supporting clients through API evolution. We use semantic versioning (major.minor.patch) to communicate the scope and impact of changes while ensuring a smooth transition between versions.
Version Control Strategy
Our versioning strategy ensures backward compatibility while enabling API evolution. We maintain clear upgrade paths and support multiple versions simultaneously to accommodate different client needs.
Versioning Schema
Defines how we structure and identify different versions of the API using semantic versioning.
interface APIVersion {
major: number; // Breaking changes
minor: number; // Backward-compatible features
patch: number; // Bug fixes
status: 'alpha' | 'beta' | 'stable' | 'deprecated';
}
// Example: v2.1.3
const VERSION_PATTERN = /^v(\d+)\.(\d+)\.(\d+)$/;
URI Versioning
Implements version routing in API endpoints through URL-based versioning.
// Base URL structure
const API_URL = 'https://api.example.com/v{major}/{resource}';
// Version routing middleware
const versionRouter = (req: Request, res: Response, next: NextFunction) => {
const version = req.path.split('/')[1]; // e.g., 'v2'
const [major] = version.match(VERSION_PATTERN) || [];
req.apiVersion = {
major: parseInt(major),
implementation: getVersionImplementation(major)
};
next();
};
Change Classifications
We classify API changes based on their impact on existing clients, which determines version increments and communication strategies.
1. Breaking Changes
Changes that require client updates.updates and trigger a major version increment.
interface BreakingChange {
type: 'BREAKING';
changes: {
removedFields?: string[];
modifiedFields?: {
field: string;
oldType: string;
newType: string;
}[];
removedEndpoints?: string[];
authenticationChanges?: string[];
};
migrationGuide: string;
effectiveDate: Date;
}
// Example breaking changes
const BREAKING_CHANGES = {
'v2.0.0': {
type: 'BREAKING',
changes: {
removedFields: ['deprecated_field'],
modifiedFields: [{
field: 'status',
oldType: 'string',
newType: 'enum'
}]
},
migrationGuide: 'docs/migration-v1-to-v2.md',
effectiveDate: new Date('2024-12-01')
}
};
2. Non-Breaking Changes
Backward-compatible additions orresulting modifications.in minor version increments.
interface NonBreakingChange {
type: 'NON_BREAKING';
changes: {
addedFields?: string[];
addedEndpoints?: string[];
optionalParameters?: string[];
enhancedResponses?: string[];
};
announcementDate: Date;
}
3. Bug Fixes
Corrections that don't affect the API contract.contract, resulting in patch version increments.
interface BugFix {
type: 'BUG_FIX';
fixes: {
description: string;
affectedEndpoints: string[];
resolution: string;
}[];
deploymentDate: Date;
}
Version Implementation
Manages different API versions within the codebase while maintaining clean separation between versions.
Version Manager
class APIVersionManager {
private versions: Map<number, Implementation>;
constructor() {
this.versions = new Map();
this.initializeVersions();
}
private initializeVersions() {
// Register version implementations
this.versions.set(1, new V1Implementation());
this.versions.set(2, new V2Implementation());
}
public getImplementation(version: number): Implementation {
const impl = this.versions.get(version);
if (!impl) {
throw new Error(`Version ${version} not supported`);
}
return impl;
}
public isDeprecated(version: number): boolean {
return this.versions.get(version)?.status === 'deprecated';
}
}
Version-Specific Implementations
interface Implementation {
version: number;
status: 'active' | 'deprecated';
handlers: Map<string, RequestHandler>;
}
class V2Implementation implements Implementation {
version = 2;
status = 'active';
handlers = new Map([
['createVault', this.createVaultV2],
['getVault', this.getVaultV2]
]);
async createVaultV2(req: Request, res: Response) {
// V2 implementation
}
async getVaultV2(req: Request, res: Response) {
// V2 implementation
}
}
API Documentation
OpenAPI Versioning
openapi: 3.0.0
info:
version: 2.0.0
title: Vault API
servers:
- url: https://api.example.com/v2
paths:
/vaults:
post:
summary: Create Vault (v2)
description: |
Changes from v1:
- Added required 'security' field
- Modified response structure
components:
schemas:
VaultV2:
allOf:
- $ref: '#/components/schemas/VaultV1'
- type: object
properties:
security:
type: object
required: true
Change Management Process
1. Planning Phase
Structured approach to planning and assessing API changes.
interface ChangeProposal {
type: 'BREAKING' | 'NON_BREAKING' | 'BUG_FIX';
description: string;
justification: string;
impact: {
clients: string[];
endpoints: string[];
estimatedEffort: string;
};
timeline: {
developmentStart: Date;
betaRelease: Date;
stableRelease: Date;
deprecationDate?: Date;
};
}
2. Communication Strategy
Clear communication of changes to API clients.
interface VersionAnnouncement {
version: string;
type: 'NEW_VERSION' | 'DEPRECATION' | 'END_OF_LIFE';
details: {
summary: string;
changes: string[];
migrationGuide?: string;
timeline: {
announcementDate: Date;
effectiveDate: Date;
endOfSupportDate?: Date;
};
};
distributionChannels: string[];
}
3. Implementation Guidelines
Version Transition
// Graceful version handling
app.use('/v:version/*', (req, res, next) => {
const version = parseInt(req.params.version);
if (version < MINIMUM_SUPPORTED_VERSION) {
return res.status(410).json({
error: 'Version no longer supported',
minimumVersion: MINIMUM_SUPPORTED_VERSION,
migrationGuide: getMigrationGuideUrl(version)
});
}
if (isDeprecated(version)) {
res.setHeader('Warning', '299 - "Deprecated API Version"');
}
next();
});
Feature Flags
interface FeatureFlag {
name: string;
enabled: boolean;
version: number;
rolloutPercentage: number;
conditions?: {
clientIds?: string[];
environments?: string[];
startDate?: Date;
};
}
const featureManager = {
isEnabled: (feature: string, context: RequestContext): boolean => {
const flag = getFeatureFlag(feature);
if (!flag) return false;
return (
flag.enabled &&
meetsVersionRequirement(context.version, flag.version) &&
meetsRolloutCriteria(context, flag.rolloutPercentage) &&
meetsConditions(context, flag.conditions)
);
}
};
Deprecation Strategy
1. Deprecation Timeline
interface DeprecationSchedule {
version: string;
announceDate: Date;
deprecationDate: Date;
endOfLifeDate: Date;
alternativeVersion: string;
migrationDeadline: Date;
}
const DEPRECATION_POLICY = {
minimumNoticePeroid: 180, // days
supportWindow: 365, // days
maxVersionsSupported: 2
};
2. Deprecation Notices
Implementation of standardized deprecation warnings.
// Response headers
app.use((req, res, next) => {
const version = req.apiVersion;
if (isDeprecated(version)) {
res.setHeader('Deprecation', 'true');
res.setHeader('Sunset', getEndOfLifeDate(version));
res.setHeader('Link', `<${getUpgradeGuideUrl(version)}>; rel="deprecation"`);
}
next();
});
3. Migration Support
interface MigrationTool {
validateRequest: (req: Request, targetVersion: number) => ValidationResult;
transformRequest: (req: Request, sourceVersion: number, targetVersion: number) => Request;
transformResponse: (res: Response, sourceVersion: number, targetVersion: number) => Response;
generateMigrationPlan: (currentVersion: number, targetVersion: number) => MigrationStep[];
}
Monitoring and Analytics
Version
Track UsageAPI Trackingversion usage to inform deprecation decisions.
interface VersionMetrics {
version: string;
requests: number;
uniqueClients: number;
errorRate: number;
avgResponseTime: number;
deprecationStatus: 'active' | 'deprecated' | 'sunset';
}
const trackVersionUsage = async (req: Request, res: Response, next: NextFunction) => {
const startTime = Date.now();
const version = req.apiVersion;
res.on('finish', () => {
const duration = Date.now() - startTime;
metrics.recordVersionUsage({
version,
duration,
status: res.statusCode,
clientId: req.auth?.clientId
});
});
next();
};
Best Practices
-
Version Selection
- Use semantic versioning consistently
- Make breaking changes only in major versions
- Maintain at least one previous major version
-
Change Management
- Plan changes well in advance
- Provide detailed migration guides
- Use feature flags for gradual rollouts
-
Communication
- Announce changes early
- Provide clear upgrade paths
- Maintain comprehensive documentation
-
Support
- Offer migration tools
- Provide support during transitions
- Monitor version usage patterns
-
Maintenance
- Regular deprecation reviews
- Clean up deprecated features
- Archive old documentation
Implementation Checklist
- Define versioning strategy
- Set up version routing
- Implement version manager
- Create deprecation policy
- Configure monitoring
- Prepare communication templates
- Document upgrade paths
- Set up automated testing