FastAPI Training: Versioning APIs Explained
Key Concepts
Versioning APIs involves several key concepts:
- URL Versioning: Including the version number in the URL.
- Header Versioning: Using custom headers to specify the API version.
- Query Parameter Versioning: Passing the version number as a query parameter.
- Content Negotiation Versioning: Using the Accept header to specify the version.
- Namespace Versioning: Organizing API versions under different namespaces.
- Versioning with Decorators: Using decorators to manage API versions.
- Versioning with Routers: Using FastAPI routers to handle different versions.
- Semantic Versioning: Following a versioning scheme like SemVer.
Explaining Each Concept
1. URL Versioning
URL Versioning involves including the version number in the URL. This is straightforward but can clutter the URL.
Example:
from fastapi import FastAPI app = FastAPI() @app.get("/v1/items/") async def read_items_v1(): return {"version": "v1", "items": ["item1", "item2"]} @app.get("/v2/items/") async def read_items_v2(): return {"version": "v2", "items": ["itemA", "itemB"]}
2. Header Versioning
Header Versioning uses custom headers to specify the API version. This keeps the URL clean but requires clients to send the header.
Example:
from fastapi import FastAPI, Header app = FastAPI() @app.get("/items/") async def read_items(version: str = Header(None)): if version == "v1": return {"version": "v1", "items": ["item1", "item2"]} elif version == "v2": return {"version": "v2", "items": ["itemA", "itemB"]} return {"error": "Unsupported version"}
3. Query Parameter Versioning
Query Parameter Versioning passes the version number as a query parameter. This is simple but can make the URL less readable.
Example:
from fastapi import FastAPI, Query app = FastAPI() @app.get("/items/") async def read_items(version: str = Query(None)): if version == "v1": return {"version": "v1", "items": ["item1", "item2"]} elif version == "v2": return {"version": "v2", "items": ["itemA", "itemB"]} return {"error": "Unsupported version"}
4. Content Negotiation Versioning
Content Negotiation Versioning uses the Accept header to specify the version. This is more RESTful but can be complex to implement.
Example:
from fastapi import FastAPI, Header app = FastAPI() @app.get("/items/") async def read_items(accept: str = Header(None)): if "application/vnd.example.v1" in accept: return {"version": "v1", "items": ["item1", "item2"]} elif "application/vnd.example.v2" in accept: return {"version": "v2", "items": ["itemA", "itemB"]} return {"error": "Unsupported version"}
5. Namespace Versioning
Namespace Versioning organizes API versions under different namespaces. This keeps the codebase clean but can be complex to manage.
Example:
from fastapi import FastAPI from fastapi.routing import APIRouter app = FastAPI() v1_router = APIRouter() v2_router = APIRouter() @v1_router.get("/items/") async def read_items_v1(): return {"version": "v1", "items": ["item1", "item2"]} @v2_router.get("/items/") async def read_items_v2(): return {"version": "v2", "items": ["itemA", "itemB"]} app.include_router(v1_router, prefix="/v1") app.include_router(v2_router, prefix="/v2")
6. Versioning with Decorators
Versioning with Decorators uses custom decorators to manage API versions. This is flexible but can be complex to implement.
Example:
from fastapi import FastAPI, Header app = FastAPI() def versioned_endpoint(version): def decorator(func): async def wrapper(*args, **kwargs): if kwargs.get("version") == version: return await func(*args, **kwargs) return {"error": "Unsupported version"} return wrapper return decorator @app.get("/items/") @versioned_endpoint("v1") async def read_items_v1(version: str = Header(None)): return {"version": "v1", "items": ["item1", "item2"]} @app.get("/items/") @versioned_endpoint("v2") async def read_items_v2(version: str = Header(None)): return {"version": "v2", "items": ["itemA", "itemB"]}
7. Versioning with Routers
Versioning with Routers uses FastAPI routers to handle different versions. This is modular and easy to manage.
Example:
from fastapi import FastAPI from fastapi.routing import APIRouter app = FastAPI() v1_router = APIRouter() v2_router = APIRouter() @v1_router.get("/items/") async def read_items_v1(): return {"version": "v1", "items": ["item1", "item2"]} @v2_router.get("/items/") async def read_items_v2(): return {"version": "v2", "items": ["itemA", "itemB"]} app.include_router(v1_router, prefix="/v1") app.include_router(v2_router, prefix="/v2")
8. Semantic Versioning
Semantic Versioning follows a versioning scheme like SemVer (Major.Minor.Patch). This provides clear guidelines for versioning.
Example:
from fastapi import FastAPI app = FastAPI() @app.get("/items/") async def read_items(): return {"version": "1.2.3", "items": ["item1", "item2"]}
Analogies
Think of URL Versioning as labeling different shelves in a library, each shelf representing a version. Header Versioning is like checking out a book with a special sticker indicating its version. Query Parameter Versioning is like searching for a book by its version number in the library catalog. Content Negotiation Versioning is like requesting a specific edition of a book when checking it out. Namespace Versioning is like organizing books into different sections, each section representing a version. Versioning with Decorators is like using special bookmarks to indicate the version of the book. Versioning with Routers is like having different librarians for each version of the book. Semantic Versioning is like following a standard book numbering system to indicate different editions.
By mastering these versioning techniques, you can effectively manage and evolve your FastAPI applications over time.