campverse-backend / Middleware /apiVersioning.js
CampVerse CI
Deploy backend from GitHub Actions
c904036
Raw History Blame Contribute Delete
3.59 kB
/**
* API Versioning Middleware
* Provides versioned API routes for backward compatibility
*
* Usage:
* - Current version: /api/v1/...
* - Legacy (unversioned): /api/... (redirects to v1)
*
* Version Header Support:
* - Accept-Version: v1
* - X-API-Version: v1
*/
const { logger } = require('../Middleware/errorHandler');
// Supported API versions
const SUPPORTED_VERSIONS = ['v1'];
const DEFAULT_VERSION = 'v1';
const CURRENT_VERSION = 'v1';
/**
* Extract API version from request
* Priority: URL path > Accept-Version header > X-API-Version header > default
*/
function extractVersion(req) {
// Check URL path for version (e.g., /api/v1/users)
const pathMatch = req.path.match(/^\/api\/(v\d+)\//);
if (pathMatch && SUPPORTED_VERSIONS.includes(pathMatch[1])) {
return pathMatch[1];
}
// Check Accept-Version header
const acceptVersion = req.headers['accept-version'];
if (acceptVersion && SUPPORTED_VERSIONS.includes(acceptVersion)) {
return acceptVersion;
}
// Check X-API-Version header
const xApiVersion = req.headers['x-api-version'];
if (xApiVersion && SUPPORTED_VERSIONS.includes(xApiVersion)) {
return xApiVersion;
}
return DEFAULT_VERSION;
}
/**
* API versioning middleware
* Attaches version info to request and sets response headers
*/
function apiVersioning(req, res, next) {
const version = extractVersion(req);
// Attach version to request
req.apiVersion = version;
// Set response headers
res.setHeader('X-API-Version', version);
res.setHeader('X-API-Current-Version', CURRENT_VERSION);
// Deprecation warning for old versions (when we have v2+)
if (version !== CURRENT_VERSION) {
res.setHeader('X-API-Deprecated', 'true');
res.setHeader('Warning', '299 - "API version deprecated, please upgrade"');
}
next();
}
/**
* Create versioned router
* Wraps an Express router with version-specific path prefix
*/
function createVersionedRouter(app, version, router) {
app.use(`/api/${version}`, router);
logger.info(`Registered API ${version} routes`);
}
/**
* Legacy route redirect middleware
* Redirects unversioned /api/... to /api/v1/...
* Only for routes that don't already have a version
*/
function legacyRouteHandler(req, res, next) {
// Skip if already versioned
if (req.path.match(/^\/api\/v\d+\//)) {
return next();
}
// Skip static assets and special routes
const skipPaths = ['/api/health', '/api/docs', '/healthz'];
if (skipPaths.some(path => req.path.startsWith(path))) {
return next();
}
// For now, just continue (backward compatible)
// In future, can redirect or log deprecation warnings
req.apiVersion = DEFAULT_VERSION;
next();
}
/**
* Version check middleware for specific minimum version requirement
* @param {string} minVersion - Minimum required version (e.g., 'v2')
*/
function requireMinVersion(minVersion) {
return (req, res, next) => {
const currentVersionNum = parseInt(req.apiVersion?.replace('v', '') || '1', 10);
const requiredVersionNum = parseInt(minVersion.replace('v', ''), 10);
if (currentVersionNum < requiredVersionNum) {
return res.status(400).json({
success: false,
error: `This endpoint requires API version ${minVersion} or higher`,
currentVersion: req.apiVersion,
requiredVersion: minVersion
});
}
next();
};
}
module.exports = {
apiVersioning,
createVersionedRouter,
legacyRouteHandler,
requireMinVersion,
extractVersion,
SUPPORTED_VERSIONS,
DEFAULT_VERSION,
CURRENT_VERSION
};