How to Version a Data API with FastAPI and OpenAPI
This tutorial shows how to version a Data API with FastAPI and OpenAPI in a practical and useful way. Knowing how to version an API allows introducing changes without breaking clients, documenting differences and managing migrations with less risk.
Prerequisites
- Python 3.9+ and pip installed
- Basic knowledge of FastAPI and HTTP
- Code editor and terminal
Step 1: prepare the environment
Create a virtual environment and install FastAPI and Uvicorn. We use minimal dependencies to focus on versioning.
python -m venv .venv
source .venv/bin/activate # ou .venv\Scripts\activate no Windows
pip install fastapi uvicorn
Step 2: create routers separated by version (path versioning)
A simple and explicit approach is to use prefixes like /v1 and /v2. Each router has its own implementation, allowing internal changes without cross-version impact.
# app/main.py
from fastapi import FastAPI
from .routers import v1, v2
app = FastAPI(title="API de Dados - Exemplo")
app.include_router(v1.router, prefix="/v1", tags=["v1"])
app.include_router(v2.router, prefix="/v2", tags=["v2"])
# routers/v1.py
from fastapi import APIRouter
router = APIRouter()
@router.get("/items")
def list_items():
# resposta simples v1
return {"version": "v1", "items": ["a", "b"]}
# routers/v2.py
from fastapi import APIRouter
router = APIRouter()
@router.get("/items")
def list_items():
# resposta diferente em v2 (exemplo: items com id)
return {"version": "v2", "items": [{"id":1, "name":"a"}, {"id":2, "name":"b"}]}
Step 3: version negotiation via header (header versioning)
Some teams prefer to negotiate the version with a header to keep URLs stable. Here we show a simple middleware that reroutes to the appropriate router using a custom Accept-Version header.
# app/version_middleware.py
from starlette.middleware.base import BaseHTTPMiddleware
from fastapi import Request
from fastapi.responses import JSONResponse
class VersionRoutingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
version = request.headers.get("Accept-Version")
# se existir cabeçalho, prefixa a path com /v{n}
if version and not request.url.path.startswith(f"/v{version}"):
# reescreve o scope para simular um pedido a /v{version}/...
new_path = f"/v{version}{request.url.path}"
request.scope["path"] = new_path
request.scope["raw_path"] = new_path.encode()
return await call_next(request)
# registar no main.py
from .version_middleware import VersionRoutingMiddleware
app.add_middleware(VersionRoutingMiddleware)
Step 4: document differences with OpenAPI and metadata
FastAPI generates OpenAPI automatically. Use description, tags and operationId to clarify differences between versions and add deprecated when necessary.
# exemplo de endpoint com deprecação
@router.get("/items", deprecated=True, summary="Listar items (v1 - obsoleto)")
def list_items():
return {"version": "v1", "items": []}
# no FastAPI, a documentação está em /docs (Swagger UI) e /redoc
Step 5: strategies for compatibility and migration
Define internal rules: what is a breaking change? Contract changes (fields, types) are breaking. To minimize impact:
- Prefer adding fields instead of removing them
- Mark obsolete endpoints with deprecated and keep them for a period
- Document differences clearly in the OpenAPI and changelogs
- If needed, provide an informative header (e.g.: X-Deprecation-Date) to warn clients
Verify the result
Start the application with Uvicorn and test both types of versioning.
uvicorn app.main:app --reload
# path versioning
curl http://127.0.0.1:8000/v1/items
curl http://127.0.0.1:8000/v2/items
# header versioning (aplicando middleware)
curl -H "Accept-Version: 2" http://127.0.0.1:8000/items
# abrir documentação
# https://127.0.0.1:8000/docs (Swagger UI)
Check that the output differs according to the version and that /docs shows separate tags for v1 and v2. Test deprecation by observing the deprecated field on endpoints.
Conclusion
Versioning a Data API with FastAPI and OpenAPI allows managing changes in a controlled way: use path versioning for clarity and header versioning for stable URLs, document in OpenAPI and mark obsolete endpoints. Next step: add automated tests that validate compatibility between versions (contract tests). Tip: start by defining a versioning policy (semver or just major) before publishing the first version.