Skip to content

fastapi-easy-versioning

Versioned APIs for FastAPI. One sub-application per version, automatic inheritance of endpoints from older versions into newer ones, and an up-to-date OpenAPI schema for every version.


Why

Once an API lives in several versions, routes have to be copied between them by hand: /v2 must serve everything /v1 served, minus what was deliberately dropped. The copies drift apart, /v1/docs and /v2/docs start lying, and "which versions is this endpoint even in?" becomes a question for git blame.

fastapi-easy-versioning makes the availability range a property of the endpoint: declare it once, mark it with a dependency, and it shows up in every later version by itself.

app_v1 = FastAPI()
app_v2 = FastAPI()


@app_v1.get("/items")
def items_v1() -> list[Item]:  # (1)!
    return get_items()


@app_v2.get("/items")
def items_v2() -> list[Item]:
    return get_items()
  1. The same endpoint, duplicated by hand. Every new version adds another copy, and one day one of them will be left un-updated.
app = FastAPI()
app.add_middleware(VersioningMiddleware)
app.mount("/v1", app_v1)
app.mount("/v2", app_v2)


@app_v1.get("/items", dependencies=[Depends(versioning())])  # (1)!
def items() -> list[Item]:
    return get_items()
  1. Declared once in v1 — and available in v2 and every future version. The /v2/docs schema builds itself.
@app_v1.get("/legacy", dependencies=[Depends(versioning(until=2))])  # (1)!
def legacy() -> str:
    return "gone after v2"
  1. Available in v1 and v2, absent from v3 — at runtime and in the schema alike. See the until semantics.

Every version stays an ordinary FastAPI application: its own routing, its own /docs, its own dependency_overrides.

graph TD
    A[Availability by version]
    A --> B[v1]
    A --> C[v2]

    B --> B1["/only-v1 ✓"]:::available
    B --> B2["/all-versions ✓"]:::available
    B --> B3["/from-v2 ✗"]:::missing

    C --> C1["/only-v1 ✗"]:::missing
    C --> C2["/all-versions ✓"]:::available
    C --> C3["/from-v2 ✓"]:::available

    classDef available fill:#90EE90,stroke:#333,color:#1b1b1b
    classDef missing fill:#FFB6C1,stroke:#333,color:#1b1b1b

Features

  • Inheritance between versions


    An endpoint is declared once and lands in every later version on its own. Each version gets its own copy of the route.

    Middleware

  • An exact availability range


    until sets the last version an endpoint is available in. Without it — "through the latest version", including ones that do not exist yet.

    until semantics

  • Redefinition shadows


    An endpoint of your own on the same path in a newer version shadows the inherited one — at runtime and in OpenAPI.

    Inheritance rules

  • An honest /docs per version


    After inheritance every version's OpenAPI schema is regenerated, so Swagger shows exactly what that version serves.

    OpenAPI

  • HTTP and WebSocket


    APIRoute and APIWebSocketRoute are versioned alike; the version metadata is readable right inside the endpoint.

    Dependency

  • Several independent APIs


    Public and private APIs in one application version separately — one middleware per aggregating application.

    Example

Install

uv add fastapi-easy-versioning
pip install fastapi-easy-versioning

Requirements

  • Python 3.10+
  • FastAPI ≥ 0.95 (0.137.0 and 0.137.1 are excluded — see Limitations)
  • Nothing else: fastapi is the only runtime dependency

Next

  • Quickstart — a working app in a minute.
  • Guide — dependency, middleware, recipes, limitations.
  • Examples — runnable apps from the repository.
  • API Reference — signatures from the docstrings.