Middleware
VersioningMiddleware does the core work of versioning: it finds the version sub-applications, inherits the marked endpoints from older versions into newer ones and rebuilds each version's OpenAPI schema.
Where to Add It
The middleware is added only to the application that directly mounts the version sub-applications — not to the versions themselves.
from fastapi import FastAPI, Depends
from fastapi_easy_versioning import VersioningMiddleware, versioning
app = FastAPI()
app_v1 = FastAPI(api_version=1)
app_v2 = FastAPI(api_version=2)
app.mount("/v1", app_v1)
app.mount("/v2", app_v2)
app.add_middleware(VersioningMiddleware)
If you need two or more isolated versioned APIs, add a separate VersioningMiddleware to each aggregating application — every instance versions only the sub-applications mounted directly under its own application:
from fastapi import Depends, FastAPI, middleware
from fastapi_easy_versioning import VersioningMiddleware
app = FastAPI()
public_app = FastAPI(middleware=[middleware.Middleware(VersioningMiddleware)])
public_v1 = FastAPI(api_version=1)
public_v2 = FastAPI(api_version=2)
private_app = FastAPI(middleware=[middleware.Middleware(VersioningMiddleware)])
private_v1 = FastAPI(api_version=1)
private_v2 = FastAPI(api_version=2)
app.mount("/api/public", public_app)
public_app.mount("/v1", public_v1)
public_app.mount("/v2", public_v2)
app.mount("/api/private", private_app)
private_app.mount("/v1", private_v1)
private_app.mount("/v2", private_v2)
Versioned Applications Configuration
The middleware identifies which FastAPI applications participate in versioning by the api_version extra parameter (the API_VERSION_KEY constant):
api_versionmust be an integer; version0is valid.- A sub-application without
api_versionis ignored: endpoints are neither inherited into it nor taken from it, even when marked with theversioning()dependency. - An
api_versionof a wrong type ("1",True,1.0) — the sub-application is also ignored and aUserWarningis emitted, so a typo does not go unnoticed.
app_v1 = FastAPI(api_version=1) # participates in versioning
internal = FastAPI() # ignored
How Inheritance Works
Inheritance is built once — on the first ASGI event. Under a real server (uvicorn) that is the lifespan startup event; when mounted inside another application or in tests it is the first request. Subsequent requests perform no additional work.
The rules:
- Only endpoints marked with
versioning()are inherited, in the range from the declaring version up to and includinguntil. - Every inheriting version receives its own copy of the route: mutating a route in one version does not affect the others, and
dependency_overridesare resolved by the application of the version serving the request. - If a newer version declares its own endpoint with the same path and methods, inheritance into it is skipped — the newer version shadows the older one both at runtime and in the OpenAPI schema.
- Both HTTP endpoints (
APIRoute) and WebSocket endpoints (APIWebSocketRoute) are versioned with the same semantics. Shadowing is kind-aware: an HTTP endpoint and a websocket on the same path do not conflict. A fastapi 0.95 nuance: WebSocket routes there have nodependenciesparameter yet, so they can only be marked for versioning with a dependency in the endpoint signature.
OpenAPI
After inheritance the middleware rebuilds each version's OpenAPI schema, so every version's /docs shows both its own and its inherited endpoints.
The rebuild can be disabled with the rebuild_openapi parameter. Endpoints are still inherited and served, but the inherited ones will not appear in the corresponding version's schema and /docs:
from fastapi import Depends, FastAPI, middleware
from fastapi_easy_versioning import VersioningMiddleware
app = FastAPI(middleware=[middleware.Middleware(VersioningMiddleware, rebuild_openapi=False)])
# or
app = FastAPI()
app.add_middleware(VersioningMiddleware, rebuild_openapi=False)
Adding Endpoints at Runtime
A versioned endpoint or a new version added after the first request will not be picked up automatically. The public rebuild_versioning function exists for this: it rebuilds the inheritance and refreshes the versions' OpenAPI schemas. The call is idempotent, and a default until is re-resolved against the new latest version.
from fastapi_easy_versioning import rebuild_versioning
# after adding routes or mounting a new version at runtime
rebuild_versioning(app) # app is the application that mounts the versions
FastAPI Compatibility
- FastAPI below 0.137 — supported: routes are walked via the flat
router.routeslist. - FastAPI 0.137.0 and 0.137.1 — excluded by the package's dependency constraints: these versions already contain the routing refactor (
include_routerno longer copies routes,router.routesbecame a tree), but the publiciter_route_contextsiteration API only appeared in 0.137.2. - FastAPI 0.137.2 and newer — supported: routes are walked via the public
iter_route_contexts, so endpoints registered throughinclude_routerare versioned correctly, including include-time prefixes and dependencies.
Compatibility is checked in CI against the minimum supported (0.95), the last pre-refactor (0.136) and the latest FastAPI versions.