Implementing API Versioning
1. Using URL Path Versioning
| Pros | Cons |
| Visible, cacheable, easy to test in browser | URL breaks across versions |
Example: Mount per-version
app.use("/api/v1", v1Router);
app.use("/api/v2", v2Router);
2. Creating Version-Specific Routers
src/routes/
├── v1/
│ ├── index.js
│ ├── users.js
│ └── posts.js
└── v2/
├── index.js
├── users.js
└── posts.js
| Strategy | Trade-off |
| Separate routers per version | Maximum freedom; some duplication |
| Adapter pattern | Shared core + version-specific transforms |
app.use("/api/users", (req, res, next) => {
const accept = req.get("Accept") || "";
const m = accept.match(/application\/vnd\.acme\.v(\d+)\+json/);
req.apiVersion = m ? Number(m[1]) : 1;
next();
});
app.get("/api/users", (req, res) => {
if (req.apiVersion === 2) return res.json(usersV2());
res.json(usersV1());
});
4. Using Query Parameter Versioning
Example: ?version=2
app.get("/api/users", (req, res) => {
const v = Number(req.query.version) || 1;
res.json(v === 2 ? usersV2() : usersV1());
});
Warning: Query versioning pollutes caching keys and is generally discouraged for production APIs.
5. Organizing Version Files
| Layout | Best For |
| Per-version folders | Few breaking changes, clear isolation |
| Shared service + view layer | Many shared business rules, only response shape differs |
| Branched repo | Long-lived legacy versions |
6. Implementing Version Middleware
Example: Negotiation middleware
function versioned(versions) {
return (req, res, next) => {
const v = req.apiVersion || 1;
const handler = versions[v] || versions.default;
if (!handler) return res.status(406).json({ error: "Version not supported" });
return handler(req, res, next);
};
}
app.get("/api/users", versioned({ 1: listUsersV1, 2: listUsersV2 }));
7. Handling Deprecated Versions
app.use("/api/v1", (req, res, next) => {
res.set({
"Deprecation": "Wed, 01 Jan 2026 00:00:00 GMT",
"Sunset": "Wed, 01 Jul 2026 00:00:00 GMT",
"Link": '</api/v2>; rel="successor-version"'
});
next();
});
8. Documenting Version Changes
| Format | Tool |
| OpenAPI / Swagger | Per-version openapi.json |
| CHANGELOG.md | Keep-a-Changelog format |
| API portal | Stoplight, Redoc, Postman |
9. Setting Default Version
Example: Latest as default
app.use("/api/users", (req, res, next) => {
if (!req.apiVersion) req.apiVersion = 2; // current latest
next();
});
10. Implementing Version Negotiation
function negotiateVersion(req, res, next) {
const fromHeader = (req.get("API-Version") || "").match(/^v?(\d+)$/);
const fromAccept = (req.get("Accept") || "").match(/v(\d+)\+json/);
const fromPath = req.baseUrl.match(/\/v(\d+)/);
req.apiVersion = Number(fromHeader?.[1] ?? fromAccept?.[1] ?? fromPath?.[1] ?? 1);
res.set("API-Version", String(req.apiVersion));
next();
}