FastAPIInteractive

Lesson 10 of 10 ยท Advanced FastAPI Patterns

API Versioning & Router Composition

Learn to version your API using APIRouter with prefixes and tags, composing multiple versioned routers into a single application.

Open this lesson in the editor โ†’

What you'll learn

  • Understand different API versioning strategies and when to use URL-based versioning
  • Create separate APIRouter instances for each API version
  • Use prefix and tags parameters to organize versioned routes
  • Compose multiple routers into a single FastAPI application with include_router

API Versioning & Router Composition

๐ŸŽฏ What You'll Learn

  • Different strategies for versioning APIs (URL, header, query parameter)
  • How to use APIRouter with prefix and tags for modular route organization
  • How to compose multiple routers into a single FastAPI application
  • Best practices for backward compatibility and deprecation

๐Ÿ“š Theory

As your API evolves, you will inevitably need to make breaking changes -- changing response formats, renaming fields, or restructuring endpoints. API versioning allows you to introduce these changes without breaking existing clients. FastAPI's APIRouter makes it straightforward to implement URL-based versioning by composing multiple versioned routers into a single application.

Why Version Your API?

Without versioning, any change to your response format or endpoint behavior breaks all existing clients. Versioning lets you:

  • Introduce breaking changes safely alongside the old version
  • Give clients time to migrate to the new version
  • Maintain multiple versions simultaneously during transition periods
  • Deprecate old versions on a published schedule

Versioning Strategies

There are three common approaches to API versioning:

1. URL Path Versioning (Recommended for most cases)

The version is part of the URL path. This is the most visible and explicit approach.

GET /api/v1/items/
GET /api/v2/items/

Advantages: Easy to understand, cache-friendly, simple to route. Disadvantages: URL changes between versions, requires client URL updates.

2. Header-Based Versioning

The version is specified in a custom request header.

GET /api/items/
Accept-Version: v2

Advantages: Clean URLs, version is metadata not resource identity. Disadvantages: Harder to test in browser, less discoverable.

3. Query Parameter Versioning

The version is a query parameter.

GET /api/items/?version=2

Advantages: Easy to test, no URL or header changes needed. Disadvantages: Can conflict with other parameters, less semantic.

APIRouter: The Building Block

APIRouter works like a mini-FastAPI application. It can have its own endpoints, dependencies, and middleware. The key parameters for versioning are prefix and tags.

from fastapi import APIRouter

v1_router = APIRouter(prefix="/api/v1", tags=["v1"])

@v1_router.get("/items/")
async def get_items():
    return [{"id": 1, "name": "Laptop"}]

The prefix parameter prepends a path to all routes on the router. The tags parameter groups endpoints in the OpenAPI documentation.

Composing Routers with include_router

Once you have defined your versioned routers, include them in the main application:

from fastapi import FastAPI

app = FastAPI(title="Versioned API")

app.include_router(v1_router)
app.include_router(v2_router)

include_router also accepts optional prefix, tags, and deprecated parameters that can override or extend the router's settings at include time.

Evolving Response Formats

A common pattern is to have v1 return simple data and v2 return enriched, paginated responses:

# V1: Simple list
@v1_router.get("/items/")
async def get_items_v1():
    return [{"id": 1, "name": "Laptop"}]

# V2: Enriched response with metadata
@v2_router.get("/items/")
async def get_items_v2():
    return {
        "data": [{"id": 1, "name": "Laptop", "category": "electronics"}],
        "total": 1,
        "version": "v2",
    }

The v2 format adds a data wrapper, item count, and version identifier. This pattern scales well as you add pagination, filtering metadata, or hypermedia links.

Backward Compatibility

When evolving your API, follow these guidelines:

  • Additive changes are safe: Adding new fields to responses does not break clients
  • Removing fields is breaking: Always version when removing response fields
  • Changing field types is breaking: Changing a field from string to integer requires a new version
  • New optional parameters are safe: Adding optional query or body parameters is backward-compatible

Deprecation Strategy

When retiring an old API version, follow these steps: announce the timeline to clients, mark routes as deprecated using deprecated=True in include_router, add deprecation headers to responses, monitor usage, and finally remove the old version after the deadline.

app.include_router(v1_router, deprecated=True)

๐Ÿ”ง Key Concepts

  • API Versioning: Managing multiple versions of your API simultaneously
  • URL Path Versioning: Embedding the version in the URL (e.g., /api/v1/)
  • APIRouter: A modular routing component with its own prefix, tags, and dependencies
  • prefix: Path prefix prepended to all routes on a router
  • tags: Labels that group endpoints in OpenAPI documentation
  • include_router: Method that mounts a router onto the main application
  • Backward Compatibility: Ensuring existing clients continue to work with API changes
  • Deprecation: The process of phasing out an old API version

๐Ÿ’ก Best Practices

  • Use URL path versioning for public APIs -- it is the most explicit and discoverable approach
  • Extract shared business logic into service functions to avoid code duplication between versions
  • Only create a new version when you have a genuinely breaking change
  • Mark deprecated versions clearly in your OpenAPI documentation
  • Give clients a reasonable migration window (typically 6-12 months) before removing old versions
  • Include a version identifier in enriched responses so clients can verify they are hitting the right version
  • Use tags consistently to organize endpoints in the auto-generated docs

๐Ÿ”— Additional Resources

Hint

Create separate APIRouter instances with prefix='/api/v1' and prefix='/api/v2'. Define your endpoints on the routers, then call app.include_router() for each one. The v2 router should return richer response objects with 'data', 'total', and 'version' fields.

Now write it

The editor runs a real FastAPI server and grades your code against this lesson's own endpoints.

Start API Versioning & Router Composition