express-version-api
v2.0.6
Published
Middleware for versioning routes/APIs in Express.js using semantic versioning like ^1.0 or ~1.2.
Downloads
297
Maintainers
Readme
express-version-api
Version-aware middleware for Express.js APIs with semantic version matching.
Why use this
- Routes requests by semantic version (
exact,~,^). - Supports extraction from header, query, path, or custom function.
- Handles missing or unmatched versions with configurable fallback strategies.
- Exposes
req.versionInfometadata for observability and debugging. - Fully typed for TypeScript projects.
Installation
npm
npm install express-version-apiJSR
deno add jsr:@braian-quintian/express-version-apiRequirements:
- Node.js >= 18
- Express >= 4
Quick Start
import express from 'express';
import { versioningMiddleware } from 'express-version-api';
const app = express();
app.get(
'/api/users',
versioningMiddleware({
'^1': (_req, res) => res.json({ version: '1.x', users: [] }),
'^2': (_req, res) => res.json({ version: '2.x', users: [], meta: { total: 0 } }),
'3.0.0': (_req, res) => res.json({ version: '3.0.0', users: [], paging: null }),
})
);
app.use((error, _req, res, _next) => {
res.status(500).json({ error: error.message });
});Request examples:
curl -H "Accept-Version: 1.2.0" http://localhost:3000/api/users
curl -H "Accept-Version: 2.5.1" http://localhost:3000/api/users
curl -H "Accept-Version: 3.0.0" http://localhost:3000/api/usersImport Modes
ESM (default + named):
import versioningMiddleware, { parseVersion } from 'express-version-api';CommonJS (named only):
const { versioningMiddleware, parseVersion } = require('express-version-api');Version Matching Rules
Supported handler keys:
- Exact:
1.2.3 - Caret:
^1,^1.2,^1.2.3 - Tilde:
~1.2,~1.2.3
Matching priority when multiple handlers match:
- Exact
- Tilde
- Caret
Semantics:
- Exact: must match the same
major.minor.patch. - Tilde (
~): same major and minor, patch must be>=requested patch. - Caret (
^): semver-compatible range. - Caret with
0.xfollows strict semver-compatible behavior.
Examples:
^1.2.3matches1.2.3,1.3.0,1.9.9, but not2.0.0.^0.2.3matches0.2.3to0.2.x, but not0.3.0.~2.1.4matches2.1.4to2.1.x, but not2.2.0.
How Version Extraction Works
The middleware resolves version in this order:
req.versionif already set by upstream middleware (source =custom)- Configured extraction sources in order (
extraction.sources)
Default source order:
- Header:
accept-version - Query:
v
You can configure header, query, path, and custom extractors.
versioningMiddleware(
{
'^1': v1Handler,
'^2': v2Handler,
},
{
extraction: {
sources: ['header', 'query', 'path', 'custom'],
header: { name: 'x-api-version' },
query: { name: 'version' },
path: { pattern: /\/api\/v(\d+(?:\.\d+)?(?:\.\d+)?)\// },
custom: (req) => (req.hostname.startsWith('v2.') ? '2.0.0' : undefined),
},
}
);Fallback Strategies
Fallback is used when no handler matches, and also when version is missing with requireVersion: false.
none: respond withVERSION_NOT_FOUND.latest: execute the handler with the highest available version.default: executedefaultHandlerif provided, otherwise fallback tolatest.
versioningMiddleware(handlers, {
fallbackStrategy: 'default',
defaultHandler: (_req, res) => {
res.status(200).json({ fallback: true });
},
});Error Handling
Error codes emitted by the middleware:
MISSING_VERSIONINVALID_VERSION_FORMATVERSION_NOT_FOUNDINVALID_CONFIGURATIONINVALID_HANDLER
Default status codes:
- Missing version:
422 - Version not found:
422 - Invalid version format:
400
Default response shape:
{
"error": "VERSION_NOT_FOUND",
"message": "No handler found for the requested API version",
"requestedVersion": "99.0.0",
"availableVersions": ["^1", "^2"]
}Configure custom messages/statuses and override handling via onError:
versioningMiddleware(handlers, {
errorResponse: {
missingVersionStatus: 400,
missingVersionMessage: 'Version header is required',
versionNotFoundMessage: (version) => `Version ${version} is not supported`,
onError: (error, _req, res) => {
if (error.code === 'INVALID_VERSION_FORMAT') {
res.status(400).json({ code: error.code, detail: error.message });
return true;
}
return false;
},
},
});Request Metadata (req.versionInfo)
When attachVersionInfo is true, the middleware adds:
{
requested: string;
matched: string;
source: 'header' | 'query' | 'path' | 'custom';
}Notes:
sourcepreserves the real extraction source, including fallback scenarios.- When no version was provided and fallback is used,
versionInfois not attached becauserequestedis undefined.
Runtime Defaults
{
extraction: {
sources: ['header', 'query'],
header: { name: 'accept-version' },
query: { name: 'v' },
path: { pattern: /\/v(\d+(?:\.\d+)?(?:\.\d+)?)\// },
custom: undefined
},
errorResponse: {
missingVersionStatus: 422,
versionNotFoundStatus: 422,
missingVersionMessage: 'API version is required',
versionNotFoundMessage: 'No handler found for the requested API version',
includeRequestedVersion: true,
onError: undefined
},
fallbackStrategy: 'default',
defaultHandler: undefined,
validateHandlers: true,
attachVersionInfo: true,
requireVersion: true
}Public API
Main middleware:
versioningMiddleware
Error helpers:
VersioningErrorcreateMissingVersionErrorcreateVersionNotFoundErrorcreateInvalidVersionFormatErrorcreateInvalidHandlerErrorcreateInvalidConfigurationErrorisVersioningErrorhasErrorCode
Version utilities:
parseVersionparseVersionRangeversionSatisfiescompareVersionsfindLatestVersionisValidVersionisValidVersionRangenormalizeVersion
Config export:
DEFAULT_CONFIG
Breaking Changes (v2)
- Removed legacy
createVersionMiddlewareAPI. - CommonJS default export was removed; CJS must use named imports.
Development
npm install
npm run validateMain scripts:
npm run typechecknpm run lintnpm run format:checknpm run testnpm run test:coveragenpm run buildnpm run test:types
License
MIT
