Skip to content

FastAPI Route Requests

Overview

Routing is a core component of web applications, defining the mapping relationship between URL paths and handler functions. FastAPI provides a powerful and flexible routing system, supporting various HTTP methods, path parameters, query parameters, and more. This chapter will详细介绍 FastAPI's routing mechanism and request processing.

🛣️ Basic Routing Concepts

HTTP Method Support

FastAPI supports all standard HTTP methods:

python
from fastapi import FastAPI

app = FastAPI()

# GET request - Get resource
@app.get("/items")
async def get_items():
    return {"message": "Get all items"}

# POST request - Create resource
@app.post("/items")
async def create_item():
    return {"message": "Create new item"}

# PUT request - Update resource (complete update)
@app.put("/items/{item_id}")
async def update_item(item_id: int):
    return {"message": f"Update item {item_id}"}

# PATCH request - Partial update resource
@app.patch("/items/{item_id}")
async def patch_item(item_id: int):
    return {"message": f"Partially update item {item_id}"}

# DELETE request - Delete resource
@app.delete("/items/{item_id}")
async def delete_item(item_id: int):
    return {"message": f"Delete item {item_id}"}

# HEAD request - Get response headers (no response body)
@app.head("/items/{item_id}")
async def head_item(item_id: int):
    return {"message": "HEAD request"}

# OPTIONS request - Get supported methods
@app.options("/items")
async def options_items():
    return {"allowed_methods": ["GET", "POST", "PUT", "DELETE"]}

Route Decorator Details

python
from fastapi import FastAPI, status
from typing import List, Dict, Any

app = FastAPI()

# Basic route
@app.get("/")
async def root():
    return {"message": "Hello FastAPI"}

# Route with path
@app.get("/users")
async def get_users():
    return {"users": []}

# Nested path
@app.get("/api/v1/users")
async def get_api_users():
    return {"api_version": "v1", "users": []}

# Specify response status code
@app.post("/users", status_code=status.HTTP_201_CREATED)
async def create_user():
    return {"message": "User created successfully"}

# Specify response model and status code
@app.get("/users/{user_id}", status_code=200)
async def get_user(user_id: int):
    return {"user_id": user_id, "name": "John Doe"}

# Multiple decorators (same function handles multiple routes)
@app.get("/health")
@app.get("/ping")
async def health_check():
    return {"status": "healthy"}

🎯 Path Parameters Details

Basic Path Parameters

python
from fastapi import FastAPI, Path
from typing import Optional

app = FastAPI()

# Basic path parameter
@app.get("/items/{item_id}")
async def read_item(item_id: int):
    return {"item_id": item_id}

# Multiple path parameters
@app.get("/users/{user_id}/items/{item_id}")
async def read_user_item(user_id: int, item_id: int):
    return {"user_id": user_id, "item_id": item_id}

# String path parameter
@app.get("/items/{item_name}")
async def read_item_by_name(item_name: str):
    return {"item_name": item_name}

# Path parameter validation
@app.get("/items/{item_id}")
async def read_item_with_validation(
    item_id: int = Path(
        ...,  # Required parameter
        title="Item ID",
        description="Unique identifier of item to retrieve",
        ge=1,  # Greater than or equal to 1
        le=1000  # Less than or equal to 1000
    )
):
    return {"item_id": item_id}

Path Parameter Type Conversion

python
from datetime import datetime
from uuid import UUID
from enum import Enum

# Enum type parameter
class ItemType(str, Enum):
    ELECTRONICS = "electronics"
    CLOTHING = "clothing"
    BOOKS = "books"
    FOOD = "food"

@app.get("/items/{item_type}")
async def get_items_by_type(item_type: ItemType):
    return {
        "item_type": item_type,
        "message": f"Get items of {item_type.value} type"
    }

# UUID parameter
@app.get("/users/{user_id}")
async def get_user_by_uuid(user_id: UUID):
    return {"user_id": str(user_id)}

# Datetime parameter
@app.get("/orders/{order_date}")
async def get_orders_by_date(order_date: datetime):
    return {
        "order_date": order_date.isoformat(),
        "formatted_date": order_date.strftime("%Y-%m-%d %H:%M:%S")
    }

# Float parameter
@app.get("/prices/{price}")
async def get_price_info(price: float):
    return {
        "price": price,
        "tax": price * 0.1,
        "total": price * 1.1
    }

File Path Parameters

python
# Capture file path
@app.get("/files/{file_path:path}")
async def read_file(file_path: str):
    return {"file_path": file_path}

# Example:
# GET /files/documents/reports/2023/annual_report.pdf
# file_path = "documents/reports/2023/annual_report.pdf"

# File path with validation
@app.get("/static/{file_path:path}")
async def serve_static_file(
    file_path: str = Path(
        ...,
        title="File path",
        description="Relative path to static file",
        regex=r"^[a-zA-Z0-9._/\-]+$"  # Only allow specific characters
    )
):
    # Security check
    if ".." in file_path:
        raise HTTPException(status_code=400, detail="Invalid file path")

    return {"file_path": file_path, "message": "File access successful"}

🔍 Query Parameter Processing

Basic Query Parameters

python
from fastapi import FastAPI, Query
from typing import Optional, List

app = FastAPI()

# Basic query parameters
@app.get("/items/")
async def read_items(skip: int = 0, limit: int = 10):
    return {"skip": skip, "limit": limit}

# Optional query parameters
@app.get("/items/")
async def read_items_with_query(
    skip: int = 0,
    limit: int = 10,
    q: Optional[str] = None
):
    items = {"skip": skip, "limit": limit}
    if q:
        items["q"] = q
    return items

# Query parameter validation
@app.get("/items/")
async def read_items_validated(
    skip: int = Query(0, ge=0, description="Number of records to skip"),
    limit: int = Query(10, ge=1, le=100, description="Number of records to return"),
    q: Optional[str] = Query(
        None,
        min_length=3,
        max_length=50,
        regex="^[a-zA-Z0-9\s]+$",
        description="Search query string"
    )
):
    return {"skip": skip, "limit": limit, "q": q}

Advanced Query Parameters

python
from typing import List, Set
from datetime import datetime, date

# List query parameters
@app.get("/items/")
async def read_items_with_lists(
    tags: List[str] = Query([], description="Tag list"),
    categories: Set[str] = Query(set(), description="Category set")
):
    return {
        "tags": tags,
        "categories": list(categories),
        "unique_categories": len(categories)
    }

# Example call:
# GET /items/?tags=electronics&tags=gadget&categories=tech&categories=gadget
# tags = ["electronics", "gadget"]
# categories = {"tech", "gadget"}

# Boolean query parameters
@app.get("/items/")
async def filter_items(
    is_available: bool = Query(True, description="Whether available"),
    is_featured: Optional[bool] = Query(None, description="Whether featured"),
    on_sale: bool = False
):
    return {
        "is_available": is_available,
        "is_featured": is_featured,
        "on_sale": on_sale
    }

# Date query parameters
@app.get("/orders/")
async def get_orders(
    start_date: Optional[date] = Query(None, description="Start date"),
    end_date: Optional[date] = Query(None, description="End date"),
    created_after: Optional[datetime] = Query(None, description="Created after time")
):
    return {
        "start_date": start_date,
        "end_date": end_date,
        "created_after": created_after
    }

Query Parameter Aliases and Deprecation

python
# Parameter aliases
@app.get("/items/")
async def read_items_with_alias(
    item_query: Optional[str] = Query(
        None,
        alias="item-query",  # Use item-query in URL
        title="Item query",
        description="Search query string for items",
        deprecated=False
    )
):
    return {"item_query": item_query}

# Deprecated parameters
@app.get("/items/")
async def read_items_deprecated(
    q: Optional[str] = Query(None, description="Search query"),
    old_query: Optional[str] = Query(
        None,
        deprecated=True,  # Mark as deprecated
        description="Old query parameter, please use q parameter"
    )
):
    # Compatibility handling
    search_query = q or old_query
    return {"search_query": search_query}

🎭 Route Organization and Management

Using APIRouter

python
from fastapi import APIRouter, FastAPI

# Create routers
items_router = APIRouter(
    prefix="/items",
    tags=["items"],
    responses={404: {"description": "Item not found"}}
)

users_router = APIRouter(
    prefix="/users",
    tags=["users"],
    responses={404: {"description": "User not found"}}
)

# Item-related routes
@items_router.get("/")
async def get_items():
    return {"items": []}

@items_router.get("/{item_id}")
async def get_item(item_id: int):
    return {"item_id": item_id}

@items_router.post("/")
async def create_item():
    return {"message": "Item created successfully"}

# User-related routes
@users_router.get("/")
async def get_users():
    return {"users": []}

@users_router.get("/{user_id}")
async def get_user(user_id: int):
    return {"user_id": user_id}

# Main application
app = FastAPI()

# Include routers
app.include_router(items_router, prefix="/api/v1")
app.include_router(users_router, prefix="/api/v1")

# Result routes:
# GET /api/v1/items/
# GET /api/v1/items/{item_id}
# POST /api/v1/items/
# GET /api/v1/users/
# GET /api/v1/users/{user_id}

Router Nesting

python
# Create nested routers
api_v1_router = APIRouter(prefix="/api/v1")
api_v2_router = APIRouter(prefix="/api/v2")

# v1 item routes
v1_items_router = APIRouter(prefix="/items", tags=["v1-items"])

@v1_items_router.get("/")
async def get_items_v1():
    return {"version": "v1", "items": []}

# v2 item routes
v2_items_router = APIRouter(prefix="/items", tags=["v2-items"])

@v2_items_router.get("/")
async def get_items_v2():
    return {"version": "v2", "items": [], "new_features": True}

# Assemble routes
api_v1_router.include_router(v1_items_router)
api_v2_router.include_router(v2_items_router)

app = FastAPI()
app.include_router(api_v1_router)
app.include_router(api_v2_router)

# Result routes:
# GET /api/v1/items/  -> v1 version
# GET /api/v2/items/  -> v2 version

🔧 Route Configuration and Metadata

Route Metadata

python
from fastapi import FastAPI, APIRouter
from typing import Dict, Any

app = FastAPI()

# Detailed route configuration
@app.get(
    "/items/{item_id}",
    summary="Get item details",
    description="Get detailed information by item ID, including name, price, description, etc.",
    response_description="Item detail information",
    tags=["Item Management"],
    operation_id="get_item_by_id",  # OpenAPI operation ID
    deprecated=False,
    include_in_schema=True  # Whether to include in OpenAPI schema
)
async def get_item(item_id: int):
    """
    Get item details:

    - **item_id**: Unique identifier of item

    Returns detailed item information, including:
    - Item name
    - Price information
    - Description content
    - Available status
    """
    return {"item_id": item_id, "name": "Sample item"}

# Router configuration
admin_router = APIRouter(
    prefix="/admin",
    tags=["Administrator"],
    dependencies=[],  # Dependency list
    responses={
        401: {"description": "Unauthorized"},
        403: {"description": "Forbidden"},
        500: {"description": "Server error"}
    }
)

@admin_router.get(
    "/stats",
    summary="Get statistics",
    description="Get system statistics (requires admin permission)"
)
async def get_admin_stats():
    return {"users": 100, "items": 500, "orders": 1200}

app.include_router(admin_router)

Conditional Routes

python
import os
from fastapi import FastAPI

app = FastAPI()

# Add routes based on environment
DEBUG = os.getenv("DEBUG", "false").lower() == "true"

@app.get("/")
async def read_root():
    return {"message": "Production environment"}

# Only add debug routes in debug mode
if DEBUG:
    @app.get("/debug")
    async def debug_info():
        return {"debug": True, "environment": "development"}

    @app.get("/debug/config")
    async def debug_config():
        return {"config": dict(os.environ)}

# Feature flag routes
FEATURE_FLAGS = {
    "new_ui": os.getenv("ENABLE_NEW_UI", "false").lower() == "true",
    "beta_features": os.getenv("ENABLE_BETA", "false").lower() == "true"
}

if FEATURE_FLAGS["new_ui"]:
    @app.get("/ui/new")
    async def new_ui():
        return {"ui": "new_version"}

if FEATURE_FLAGS["beta_features"]:
    @app.get("/beta")
    async def beta_features():
        return {"features": ["feature_a", "feature_b"]}

🎪 Advanced Routing Features

Route Priority and Matching

python
from fastapi import FastAPI

app = FastAPI()

# Route order is important! More specific routes should come first

# 1. Most specific route
@app.get("/items/latest")
async def get_latest_items():
    return {"items": "latest"}

# 2. Specific value route
@app.get("/items/count")
async def get_items_count():
    return {"count": 42}

# 3. Path parameter route (put last)
@app.get("/items/{item_id}")
async def get_item(item_id: int):
    return {"item_id": item_id}

# Bad example (don't do this):
# If /items/{item_id} comes first,
# /items/latest will be matched as item_id="latest"

Route Conflict Resolution

python
from fastapi import FastAPI, HTTPException, Path
from typing import Union

app = FastAPI()

# Handle conflicts using union types
@app.get("/items/{item_identifier}")
async def get_item_flexible(item_identifier: str):
    # Try parsing as integer ID
    if item_identifier.isdigit():
        item_id = int(item_identifier)
        return {"type": "id", "value": item_id}

    # Handle special strings
    if item_identifier in ["latest", "popular", "featured"]:
        return {"type": "special", "value": item_identifier}

    # Handle regular strings (possibly names or codes)
    return {"type": "name", "value": item_identifier}

# Or use stricter validation
@app.get("/items/by-id/{item_id}")
async def get_item_by_id(item_id: int = Path(..., ge=1)):
    return {"item_id": item_id}

@app.get("/items/by-name/{item_name}")
async def get_item_by_name(
    item_name: str = Path(..., regex=r"^[a-zA-Z0-9_-]+$")
):
    return {"item_name": item_name}

Dynamic Route Generation

python
from fastapi import FastAPI, APIRouter

def create_crud_router(resource_name: str, resource_data: dict):
    """Dynamically create CRUD routes"""
    router = APIRouter(prefix=f"/{resource_name}", tags=[resource_name])

    @router.get("/")
    async def list_resources():
        return {f"{resource_name}": list(resource_data.values())}

    @router.get("/{resource_id}")
    async def get_resource(resource_id: int):
        if resource_id in resource_data:
            return resource_data[resource_id]
        raise HTTPException(status_code=404, detail=f"{resource_name} not found")

    @router.post("/")
    async def create_resource(data: dict):
        new_id = max(resource_data.keys()) + 1 if resource_data else 1
        resource_data[new_id] = data
        return {f"{resource_name}_id": new_id, **data}

    return router

# Create multiple resource routes
app = FastAPI()

# User resources
users_data = {1: {"name": "Alice"}, 2: {"name": "Bob"}}
users_router = create_crud_router("users", users_data)

# Product resources
products_data = {1: {"name": "Laptop"}, 2: {"name": "Mouse"}}
products_router = create_crud_router("products", products_data)

# Register routes
app.include_router(users_router, prefix="/api")
app.include_router(products_router, prefix="/api")

📊 Route Performance Optimization

Async vs Sync Routes

python
import asyncio
import time
from fastapi import FastAPI

app = FastAPI()

# Sync route (blocking)
@app.get("/sync")
def sync_endpoint():
    # Simulate CPU-intensive operation
    time.sleep(1)
    return {"message": "Sync processing complete"}

# Async route (non-blocking)
@app.get("/async")
async def async_endpoint():
    # Simulate I/O operation
    await asyncio.sleep(1)
    return {"message": "Async processing complete"}

# Async database operation example
@app.get("/async-db")
async def async_db_operation():
    # Simulate async database query
    await asyncio.sleep(0.1)  # Simulate network delay
    return {"data": "Data from async database"}

# Sync database operation example (not recommended)
@app.get("/sync-db")
def sync_db_operation():
    # Simulate sync database query
    time.sleep(0.1)  # Block other requests
    return {"data": "Data from sync database"}

Route Caching and Optimization

python
from functools import lru_cache
import asyncio

# Use caching to optimize compute-intensive operations
@lru_cache(maxsize=128)
def expensive_computation(n: int) -> int:
    """Compute Fibonacci sequence (cache results)"""
    if n <= 1:
        return n
    return expensive_computation(n-1) + expensive_computation(n-2)

@app.get("/fibonacci/{n}")
async def get_fibonacci(n: int):
    if n > 40:  # Prevent too large values
        raise HTTPException(status_code=400, detail="Value too large")

    result = expensive_computation(n)
    return {"n": n, "fibonacci": result}

# Async caching example
_cache = {}

async def async_expensive_operation(key: str):
    """Simulate async expensive operation"""
    if key in _cache:
        return _cache[key]

    # Simulate time-consuming operation
    await asyncio.sleep(0.5)
    result = f"Result: {key.upper()}"
    _cache[key] = result
    return result

@app.get("/cached/{key}")
async def get_cached_result(key: str):
    result = await async_expensive_operation(key)
    return {"key": key, "result": result, "cached": key in _cache}

Summary

This chapter detailed FastAPI's routing system, including:

  • HTTP Method Support: GET, POST, PUT, DELETE, etc.
  • Path Parameters: Type conversion, validation, file paths
  • Query Parameters: Basic usage, validation, list parameters
  • Route Organization: APIRouter, nested routes, modularization
  • Route Configuration: Metadata, conditional routes, dynamic generation
  • Performance Optimization: Async processing, caching strategies

FastAPI's routing system is very flexible and powerful, supporting complex URL patterns and parameter validation. Reasonable use of these features can build both high-performance and maintainable APIs.

Route Design Recommendations

  • Follow RESTful design principles
  • Use clear URL structure
  • Reasonably use path parameters and query parameters
  • Add appropriate validation and documentation
  • Consider route execution order
  • Prioritize async processing for I/O operations

In the next chapter, we will learn advanced usage of path parameters and query parameters.

Content is for learning and research only.