python 81 lines · 4 tabs

Versioning a FastAPI API With APIRouter Mounted Under /v1 and /v2 Prefixes

Shared by codesnips Aug 2026
4 tabs
from datetime import datetime

from pydantic import BaseModel


class UserV1(BaseModel):
    id: int
    name: str
    email: str


class UserV2(BaseModel):
    id: int
    first_name: str
    last_name: str
    email: str
    created_at: datetime
4 files · python Explain with highlit

This snippet shows how to run two versions of an API side by side in a single FastAPI application by defining each version's routes on its own APIRouter and mounting them under distinct path prefixes. The pattern keeps /v1 frozen for existing clients while /v2 evolves the response schema, without duplicating business logic or forking the service.

In schemas.py, two Pydantic models describe the wire format for each version. UserV1 exposes a single name field, reflecting the original contract. UserV2 splits that into first_name and last_name and adds created_at, a breaking change that justifies a new version. Keeping the schemas separate is the core discipline of versioning: the transport shape is decoupled from the internal domain, so a change to the API never silently reshapes stored data.

The shared domain lives in service.py. UserService.get_user returns a plain domain object that knows nothing about API versions. Both routers consume this one source of truth, so bug fixes and query logic are written once. The adapters that follow are responsible only for projecting that domain object into the correct versioned representation.

routers.py defines v1_router and v2_router, each an independent APIRouter with its own tags and its own response_model. The v1 handler maps the domain's full_name onto the flat UserV1.name, while the v2 handler splits the name and surfaces the timestamp. Both share the same get_service dependency, demonstrating that dependency injection composes cleanly across versions. Because each router owns its endpoint decorators, the two versions can diverge freely — new routes, different validation, deprecations — without touching each other.

main.py wires everything together with app.include_router, passing prefix="/api/v1" and prefix="/api/v2". FastAPI merges each router's paths under the prefix, so the same relative route lands at two URLs. This keeps the OpenAPI docs grouped by version via the tags. The main trade-off is duplicated adapter code as versions accumulate; the payoff is that old clients keep working untouched while new ones opt into the improved schema. A common pitfall is leaking shared mutable schema classes between versions — the deliberate separation in schemas.py avoids exactly that.


Related snips

Share this code

Here's the card — post it anywhere.

Versioning a FastAPI API With APIRouter Mounted Under /v1 and /v2 Prefixes — share card
Link copied