DEV Community

Cover image for Handling API Versioning in Microservices Without Breaking Everything
ar abid
ar abid

Posted on

Handling API Versioning in Microservices Without Breaking Everything

Problem:
When you change an API in a microservices system, old consumers may break. For example, removing a field or changing a response type can cause production errors.

Solution:
Use versioned endpoints and backward-compatible changes. Combine with default values and feature flags. Here’s how it works in practice.

Example: Node.js Express API

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

// Original API v1
app.get('/api/v1/user/:id', (req, res) => {
  res.json({
    id: req.params.id,
    name: "John Doe",
    email: "john@example.com"
  });
});

// Evolved API v2 - added optional field 'role'
app.get('/api/v2/user/:id', (req, res) => {
  res.json({
    id: req.params.id,
    name: "John Doe",
    email: "john@example.com",
    role: "user" // new field, safe for old consumers
  });
});

app.listen(3000, () => console.log('Server running on port 3000'));
Enter fullscreen mode Exit fullscreen mode

How it helps:

  • Old clients use /api/v1 and don’t break.
  • New clients can use /api/v2 to access new fields.
  • Optional fields or defaults prevent breaking changes.

Contract Testing Example (Node + Pact)

const { Pact } = require('@pact-foundation/pact');
const provider = new Pact({ consumer: 'FrontendApp', provider: 'UserService' });

describe('API contract', () => {
  it('should include role field in v2', async () => {
    await provider.addInteraction({
      state: 'user exists',
      uponReceiving: 'a request for user v2',
      withRequest: {
        method: 'GET',
        path: '/api/v2/user/123'
      },
      willRespondWith: {
        status: 200,
        body: {
          id: 123,
          name: 'John Doe',
          email: 'john@example.com',
          role: 'user'
        }
      }
    });
  });
});
Enter fullscreen mode Exit fullscreen mode

Benefits:

  • Ensures API changes do not break consumer expectations.
  • Automates safety for microservice evolution.

Best Practices

  1. Always version endpoints (/v1, /v2).
  2. Add new fields instead of removing old ones.
  3. Use default values for backward compatibility.
  4. Apply contract testing to validate changes.
  5. Communicate deprecations to consumers clearly.

Outcome:
This approach allows your microservices API to evolve safely without breaking existing clients. Even in large distributed systems, small changes won’t cascade into failures.

Top comments (0)