Implementing API Versioning

1. Using URL Path Versioning

ProsCons
Visible, cacheable, easy to test in browserURL 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
        
StrategyTrade-off
Separate routers per versionMaximum freedom; some duplication
Adapter patternShared core + version-specific transforms

3. Using Header-Based Versioning

Example: Accept header

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

LayoutBest For
Per-version foldersFew breaking changes, clear isolation
Shared service + view layerMany shared business rules, only response shape differs
Branched repoLong-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

Example: Deprecation header (RFC 8594)

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

FormatTool
OpenAPI / SwaggerPer-version openapi.json
CHANGELOG.mdKeep-a-Changelog format
API portalStoplight, 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

Example: Header + path fallback

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();
}