MasterController

API Versioning

Serve several versions of a path side by side and tell clients which ones exist.

master.useApiVersioning() mirrors Asp.Versioning: configurable version readers, apiVersion / deprecated on routes, groups, and minimal APIs, the requested version on this.apiVersion, api-supported-versions headers, and 400 ProblemDetails when a client asks for something you do not serve.

Setup#

javascript
master.useApiVersioning({
  defaultVersion: '1.0',
  assumeDefaultWhenUnspecified: true,
  reportApiVersions: true,
  readers: ['query:api-version', 'header:x-api-version', 'media-type:v', 'url-segment'],
});

router.route('/items', 'itemsV1#index', 'get', { apiVersion: '1.0', deprecated: true });   // [ApiVersion("1.0")] [Obsolete]
router.route('/items', 'itemsV2#index', 'get', { apiVersion: ['2.0', '3.0'] });           // [ApiVersion("2.0")] [ApiVersion("3.0")]
router.group('/api', { apiVersion: '2.0' }, (g) => g.resources('orders'));                   // group default
router.route('/v:version/reports', 'reports#index', 'get');                                  // url-segment: /v2/reports -> ctx.apiVersion '2.0'
  • defaultVersion ('1.0') — used when the request names no version and assumeDefaultWhenUnspecified is true (the default).
  • readers — any of 'query:<name>', 'header:<name>', 'media-type:<param>' (Accept: application/json;v=2.0), 'url-segment' (/v2/..., with a :version route parameter).
  • reportApiVersions (true) — adds api-supported-versions / api-deprecated-versions to versioned responses.

Declaring versions#

{ apiVersion: '2.0' | ['2.0', '3.0'], deprecated: true } on router.route(), router.group() (inherited default), router.resources(), and master.map.*. The requested version is available as this.apiVersion in actions and ctx.apiVersion in middleware.

javascript
export default class ItemsV2Controller {
  async index() {
    this.logger.info('serving {version}', { version: this.apiVersion });   // '2.0' or '3.0'
    this.ok(await this.db.Item.toList());
  }
}

// minimal APIs take the same options
master.map.get('/ping', () => ({ pong: true }), { apiVersion: '2.0' });

Resolution rules#

  • Readers run in order; the first that finds a version wins. A malformed value → 400 Invalid API version (ProblemDetails).
  • A versioned route serves only its versions. If the path exists but no route matches the requested version → 400 Unsupported API version with api-supported-versions (ASP.NET behaviour).
  • Unversioned routes serve any version.
on the wire
GET /items?api-version=2.0
GET /items            (x-api-version: 3.0)
GET /items            (Accept: application/json;v=2.0)
GET /v2/reports

# responses (reportApiVersions: true)
api-supported-versions: 1.0, 2.0, 3.0
api-deprecated-versions: 1.0

# no route for the requested version
HTTP/1.1 400 Bad Request
content-type: application/problem+json
api-supported-versions: 1.0, 2.0, 3.0
{ "title": "Unsupported API version", "status": 400, ... }
Deprecating a version
Keep the old route registered with deprecated: true: it still serves, but every versioned response advertises it in api-deprecated-versions so clients can migrate before you remove it. Mark the matching operations deprecated in static openapi so the OpenAPI document says the same.