Skip to content

FastAPI Route Patterns

Router Organization

Group routes by domain with consistent prefixes and tags:

from fastapi import APIRouter

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

Response Models and Status Codes

Always declare response_model and explicit status codes:

@router.post("/", response_model=UserResponse, status_code=201)
async def create_user(
    user_in: UserCreate,
    service: UserService = Depends(get_user_service),
) -> UserResponse:
    return await service.create_user(user_in)


@router.get("/{user_id}", response_model=UserResponse)
async def get_user(
    user_id: UUID,
    service: UserService = Depends(get_user_service),
) -> UserResponse:
    return await service.get_user(user_id)


@router.patch("/{user_id}", response_model=UserResponse)
async def update_user(
    user_id: UUID,
    user_in: UserUpdate,
    service: UserService = Depends(get_user_service),
) -> UserResponse:
    return await service.update_user(user_id, user_in)


@router.delete("/{user_id}", status_code=204)
async def delete_user(
    user_id: UUID,
    service: UserService = Depends(get_user_service),
):
    await service.delete_user(user_id)

Query Parameters and Filtering

Use Query() for validated query parameters and Depends() for filter schemas:

from fastapi import Query


@router.get("/", response_model=Page[UserResponse])
async def list_users(
    page: int = Query(1, ge=1),
    size: int = Query(20, ge=1, le=100),
    search: str | None = Query(None, max_length=200),
    is_active: bool | None = Query(None),
    service: UserService = Depends(get_user_service),
) -> Page[UserResponse]:
    return await service.list_users(
        page=page, size=size, search=search, is_active=is_active
    )

File Uploads

from fastapi import UploadFile


@router.post("/{user_id}/avatar", response_model=UserResponse)
async def upload_avatar(
    user_id: UUID,
    file: UploadFile,
    service: UserService = Depends(get_user_service),
) -> UserResponse:
    if file.content_type not in ("image/jpeg", "image/png"):
        raise BadRequestError("Only JPEG and PNG files are accepted")
    if file.size and file.size > 5 * 1024 * 1024:
        raise BadRequestError("File size must be under 5MB")
    return await service.update_avatar(user_id, file)