Skip to content

Integration support for API versioning (header strategy, deprecation, versioned documentation) #1807

@CarlosEduJs

Description

@CarlosEduJs

Motivation

When building APIs for applications (especially SaaS), changes happen, new features are added, bugs are fixed, and at some point, breaking changes inevitably show up.

Some of these modifications over the course of development can break existing integrations or impact clients that depend on the current API contract. That's why API versioning is important: it allows older versions to keep working while newer versions continue to evolve.

Currently, in Elysia, this can be done manually using prefixes or route groups:

app.group("/v1", (app) => {
  app.use(userRoutes)
})

app.group("/v2", (app) => {
  app.use(userRoutesV2)
})

This approach works well for simple cases, but doesn't cover some common patterns used in production APIs, such as:

  • Header-based versioning > keeping URLs clean and defining the version via an HTTP header
  • Clear errors for unsupported versions > when a client requests a version that doesn't exist
  • Deprecation of older versions > using HTTP headers like Deprecation and Sunset
  • Separate documentation per version > making the API easier to use when multiple versions exist

Adding optional versioning support could greatly improve the DX for anyone who needs to evolve APIs over time without breaking existing applications.


Proposal

The idea is to add optional API versioning support to Elysia.

The goal is not to replace route prefix usage, but to offer a more structured way to handle versioning.

Implementation as a plugin

An interesting approach would be to implement this as a plugin, for example @elysiajs/versioning.

This would have a few advantages:

  • keeps the framework core simple
  • follows Elysia's modular philosophy
  • lets the community test and evolve the idea
  • completely optional adoption

If the solution works well, it could eventually be integrated into the core.

Large APIs, like Stripe's, use structured versioning strategies precisely to be able to evolve without breaking clients.


Proposed features

Versioning strategies

The plugin could support different ways of defining the API version.

Header-based versioning

The API version is defined in a request header:

app.use(versioning({
  strategy: "header",
  header: "Accept-Version",
  defaultVersion: "1"
}))

Example request:

GET /users
Accept-Version: 2

Path-based versioning

The version appears directly in the URL:

app.use(versioning({
  strategy: "path",
  defaultVersion: "1"
}))

Example:

/v1/users
/v2/users

Query-based versioning

Useful for testing or internal APIs:

app.use(versioning({
  strategy: "query",
  parameter: "version",
  defaultVersion: "1"
}))

Example:

GET /users?version=2

Version on routes

Routes could define which version they belong to:

app.get("/users", handlerV1, {
  version: "1"
})

app.get("/users", handlerV2, {
  version: "2"
})

With this:

  • multiple versions can exist for the same route
  • the framework automatically picks the correct handler
  • routes without a version continue to work normally

Version deprecation

It would also be possible to mark routes or versions as deprecated:

app.get("/users", handler, {
  version: "1",
  deprecated: {
    sunset: "2027-06-01"
  }
})

Responses could include headers such as:

Deprecation: true
Sunset: Mon, 01 Jun 2027 00:00:00 GMT

This helps clients understand when they need to migrate to newer versions.


Error for unsupported versions

If a client requests a version that doesn't exist, the framework could return a clear error:

{
  "error": "Unsupported API version",
  "requestedVersion": "5",
  "supportedVersions": ["1", "2"],
  "latestVersion": "2"
}

This avoids unexpected behavior and makes debugging easier.


Per-version documentation

Integration with plugins like @elysiajs/swagger could generate separate documentation per version:

/docs/v1
/docs/v2

Or even allow choosing the version within the documentation interface itself.


Backward compatibility

This proposal is fully compatible with existing applications:

  • defining a version on routes is optional
  • existing routes continue to work normally
  • versioning is only applied when the plugin is used

Benefits

Some benefits of this approach:

  • better DX for APIs that need to evolve
  • less boilerplate for handling versioning manually
  • patterns used in real-world APIs
  • integration with the Elysia ecosystem
  • ability to evolve APIs without breaking clients

Open for discussion

This is an initial idea to discuss how structured versioning could work in Elysia.

A few questions:

  • would this make more sense as a plugin or as a core feature?
  • are there other versioning strategies worth considering?
  • how can this be better integrated with documentation plugins?

If the maintainers find the idea interesting, I'd be happy to help with the design or implementation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions