f in x
REST API Node.js — Versioning and OpenAPI Documentation for Production APIs
> cd .. / HUB_EDITORIALE
Sviluppo di siti web

REST API Node.js — Versioning and OpenAPI Documentation for Production APIs

[2026-06-23] Author: Ing. Calogero Bono

Have you ever shipped a change to an API and found that the mobile client broke? Or inherited a Node.js backend with zero documentation, spending hours reading code and guessing? If you work with REST APIs in Node.js, you'll eventually face two problems: how to evolve the API without breaking existing clients and how to keep track of what every endpoint does. Versioning and OpenAPI are not optional — they're the difference between an API that scales and a mess of endpoints nobody understands.

At Meteora Web, we see this every day in projects that come to us. APIs without versioning, documentation missing or hand‑written in an already outdated Google Doc. Then comes the chaos: clients calling deprecated endpoints, mysterious 500 errors, developers afraid to touch the backend. With Node.js and Express, we've built a clean, sustainable workflow that solves both problems.

Why version your REST APIs in Node.js?

The reason is simple: don't break clients that depend on your API. When you publish an endpoint, someone — a mobile app, a React frontend, a third‑party integration — relies on that data structure, those field names, that behaviour. Change everything without notice, and you're setting up an incident. “But we can just add a new field” — sure, but changing a required field, removing a field, or changing its type are breaking changes. Without versioning, your only alternative is never to change, turning the API into a fossil.

Sponsored Protocol

We always start with a question: how much does an integration incident cost? A day of collective debugging between frontend and backend teams costs more than an hour of versioning setup. After eight years of projects, we chose the pragmatic path.

Which versioning strategies work for Node.js APIs?

The three classic strategies are URL path, query parameter, and custom header. Here's how they perform with Express.

URL path versioning (most common)

Example: /api/v1/users, /api/v2/users. Dead simple, visible, easily testable by hand. We use this in most projects because it's transparent even to non‑technical clients.

const express = require('express');
const app = express();

// V1: returns name and email
const routerV1 = express.Router();
routerV1.get('/users/:id', (req, res) => {
  res.json({ id: req.params.id, name: 'Mario', email: 'mario@example.com' });
});

// V2: adds phone (breaking change for clients expecting only name/email)
const routerV2 = express.Router();
routerV2.get('/users/:id', (req, res) => {
  res.json({ id: req.params.id, name: 'Mario', email: 'mario@example.com', phone: '+39...' });
});

app.use('/api/v1', routerV1);
app.use('/api/v2', routerV2);

Pros: clear, cacheable, easy to version at the gateway level. Cons: many versions can bloat URL schemes and duplicate code. We isolate routers in separate files and use a versions/index.js to map the version to the correct router.

Sponsored Protocol

Header versioning (Accept or custom)

Example: header Accept: application/vnd.api+json;version=2. Cleaner from a RESTful perspective because the URL represents the resource, not its form. But it's invisible in a simple curl without explicit specification and makes logs less immediate. We reserve it for internal APIs or when the client is a well‑controlled mobile app.

app.get('/users/:id', (req, res) => {
  const version = req.headers['accept-version'] || '1';
  if (version === '2') {
    res.json({ id: req.params.id, name: 'Mario', email: 'mario@example.com', phone: '+39...' });
  } else {
    res.json({ id: req.params.id, name: 'Mario', email: 'mario@example.com' });
  }
});

Pros: clean URLs, versioning without changing routes. Cons: harder to debug, caching complexity, the client must know exactly what to send.

Query parameter versioning

/api/users?version=2. We advise against it: it pollutes the meaning of the query (which should be for filters and pagination) and is easily forgotten by the client. We avoid it.

How to document REST APIs with OpenAPI in Node.js?

Having versions is useless if you don't know what each version does. OpenAPI (formerly Swagger) solves this: a YAML/JSON file that describes every endpoint, parameters, responses, schemas. And with swagger-jsdoc and swagger-ui-express, you generate interactive documentation directly from your code.

Sponsored Protocol

We often see projects where documentation is a two‑year‑old PDF. We integrate OpenAPI into the development cycle: every route has JSDoc comments that generate the spec. Zero extra effort, documentation always in sync.

Quick setup with Express

npm install swagger-jsdoc swagger-ui-express

openapi.js configuration

const swaggerJsdoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');

const options = {
  definition: {
    openapi: '3.0.0',
    info: {
      title: 'MyApp API',
      version: '1.0.0',
      description: 'MyApp API documentation',
    },
    servers: [
      { url: 'http://localhost:3000/api/v1', description: 'Development server V1' },
      { url: 'http://localhost:3000/api/v2', description: 'Development server V2' },
    ],
  },
  apis: ['./routes/*.js'], // where you put JSDoc comments
};

const spec = swaggerJsdoc(options);

module.exports = { swaggerUi, spec };

Document a route with JSDoc

In routes/users.js:

/**
 * @openapi
 * /users/{id}:
 *   get:
 *     description: Get a user by ID
 *     parameters:
 *       - in: path
 *         name: id
 *         required: true
 *         schema:
 *           type: string
 *     responses:
 *       200:
 *         description: User found
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 id:
 *                   type: string
 *                 name:
 *                   type: string
 *                 email:
 *                   type: string
 *       404:
 *         description: User not found
 */
router.get('/users/:id', (req, res) => {
  // ...
});

Mount Swagger UI

const { swaggerUi, spec } = require('./openapi');

app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(spec));

// Optionally expose the spec as JSON
app.get('/api-docs.json', (req, res) => res.json(spec));

Now you have an interactive interface at /api-docs where every endpoint is testable. The documentation updates every time you modify the JSDoc comments.

Sponsored Protocol

How to handle multiple versions in OpenAPI documentation?

A single OpenAPI file can describe all versions using distinct paths for /api/v1/users and /api/v2/users. Or you can maintain separate files. We prefer a single spec with multiple servers and tags per version.

tags:
  - name: v1
    description: Version 1 endpoints
  - name: v2
    description: Version 2 endpoints
paths:
  /users/{id}:
    get:
      tags: [v1]
      # ...
  /users/{id}:
    get:
      tags: [v2]
      # ...

In the JSDoc implementation, tag each route accordingly. The UI shows a dropdown to choose the server.

What best practices should you follow for versioning and documentation in production?

  • Version only when there's a breaking change. Adding optional fields does not require a new version. Removing or changing a type does.
  • Deprecate old versions gradually. Include Sunset and Deprecation headers in responses of versions you plan to retire. Mark them in the spec with deprecated: true.
  • Keep no more than 2 active versions (unless you have long contracts). Maintaining old versions costs maintenance, errors, and technical debt.
  • Automate spec generation. We integrate it into our CI pipeline: on every push, the spec is validated with swagger-cli and published to an internal endpoint.
  • Use express-openapi-validator to validate requests and responses against the spec. Prevent errors in production.

What to do now

  1. Choose a versioning strategy — for 80% of projects, URL path works best. Implement it right away in your Express router.
  2. Install swagger-jsdoc and swagger-ui-express, configure the base spec. Document at least one endpoint for practice.
  3. Add JSDoc comments to all existing routes. You can do it gradually, but start with those exposed to clients.
  4. Serve the spec publicly (protect with basic auth if sensitive) and share it with frontend and mobile teams.
  5. Plan a deprecation cycle for any old versions. Add deprecation headers and communicate deadlines.

For a full working example of versioning and OpenAPI with Node.js, check out our main guide on Node.js for backend.

Ing. Calogero Bono

> AUTHOR_EXTRACTED

Ing. Calogero Bono

Ingegnere Informatico, co-fondatore di Meteora Web. Esperto in architetture software, sicurezza informatica e sviluppo sistemi scalabili.
[ Read Full Dossier ]

> METEORA_WEB // DIGITAL AGENCY

We build the digital presence your business deserves.

Websites, social media, online advertising, e-commerce and high-performance hosting, engineered with method by computer engineers in Sciacca, for all of Italy.

> MW_JOURNAL

> READ_ALL()