Complete DevOps Bootcamp: Master DevOps in 12 Weeks
FastAPIRouting and Parameters

API Versioning Basics

Introduction

As an API evolves, you may need to change how endpoints work in ways that are not backward compatible. API versioning lets you introduce these changes while keeping existing clients working. It is a common pattern in production APIs.

Why This Matters

Without versioning, every breaking change you make to the API can break existing clients. With versioning, clients that depend on version one continue to work while new clients can use version two. This makes your API more reliable and easier to maintain over time.

Common Versioning Strategies

StrategyExampleNotes
URL path versioning/v1/usersMost common, easy to understand
Query parameter/users?version=1Less common, harder to route
Header versioningAccept: application/vnd.api+v1+jsonClean URLs but harder to test

URL path versioning is the most straightforward approach and the one most commonly used with FastAPI.

URL Path Versioning

The simplest approach is to include the version number in the route path:

from fastapi import FastAPI

app = FastAPI()

@app.get("/v1/users")
def list_users_v1():
    return [{"id": 1, "name": "Alice"}]

@app.get("/v2/users")
def list_users_v2():
    return [{"id": 1, "name": "Alice", "email": "alice@example.com"}]

V2 returns more fields. V1 clients still work because /v1/users is unchanged.

Using Routers for Versioning

For larger projects, organize each version into its own router:

# routers/v1/users.py
from fastapi import APIRouter

router = APIRouter()

@router.get("/users")
def list_users():
    return [{"id": 1, "name": "Alice"}]
# routers/v2/users.py
from fastapi import APIRouter

router = APIRouter()

@router.get("/users")
def list_users():
    return [{"id": 1, "name": "Alice", "email": "alice@example.com"}]
# main.py
from fastapi import FastAPI
from routers.v1 import users as users_v1
from routers.v2 import users as users_v2

app = FastAPI()

app.include_router(users_v1.router, prefix="/v1", tags=["v1"])
app.include_router(users_v2.router, prefix="/v2", tags=["v2"])

This structure keeps version-specific code fully separated.

Using Separate FastAPI Apps

For strong isolation between versions, each version can be a separate FastAPI app mounted onto a parent app:

from fastapi import FastAPI

app = FastAPI()
v1_app = FastAPI()
v2_app = FastAPI()

@v1_app.get("/users")
def v1_users():
    return [{"id": 1, "name": "Alice"}]

@v2_app.get("/users")
def v2_users():
    return [{"id": 1, "name": "Alice", "email": "alice@example.com"}]

app.mount("/v1", v1_app)
app.mount("/v2", v2_app)

Each mounted app has its own Swagger UI at /v1/docs and /v2/docs.

Deprecating Old Versions

Mark routes in an old version as deprecated before removing them:

@app.get("/v1/users", deprecated=True, tags=["v1"])
def list_users_v1():
    return [{"id": 1, "name": "Alice"}]

This signals to API consumers that they should migrate to the newer version.

What Counts as a Breaking Change

Not every change needs a new version. A breaking change is one that forces existing clients to update their code.

Change typeBreaking?
Adding a new optional field to a responseNo
Removing a field from a responseYes
Changing a field typeYes
Adding a new endpointNo
Changing the behavior of an existing endpointYes
Adding a required request fieldYes

Versioning and Documentation

When using router-based versioning with tags, each version appears as its own section in Swagger UI, making it easy to browse what changed:

app.include_router(users_v1.router, prefix="/v1", tags=["Users V1"])
app.include_router(users_v2.router, prefix="/v2", tags=["Users V2"])

Common Mistakes

Versioning every minor change

Only create a new version for breaking changes. Additive changes such as new optional fields do not require a new version.

Never retiring old versions

Old versions need maintenance. Set a clear deprecation and removal timeline and communicate it to API consumers.

Putting version logic inside a single function

Avoid branching inside one route function to handle multiple versions. Keep versions in separate files to make the code easier to read and delete when a version is retired.

Summary

API versioning allows you to evolve your FastAPI backend without breaking existing clients. URL path versioning using prefixes and routers is the most practical approach for FastAPI projects. Mark old versions as deprecated before removal and create new versions only for breaking changes.

How is this guide?

Last updated on