
FastAPI
Master the design patterns and strategies for handling required and optional parameters in production APIs.
In REST architecture, path parameters identify the specific resource. They cannot be truly "optional" in the URL path itself. However, there are strategic patterns to handle optional data:
from fastapi import FastAPI, HTTPException
app = FastAPI()
# Specific resource
@app.get("/files/{file_id}")
async def get_file(file_id: int):
'''Get a specific file by ID'''
file = db.get_file(file_id)
if not file:
raise HTTPException(status_code=404, detail="File not found")
return file
# List all resources (handles the "no specific ID" case)
@app.get("/files")
async def list_files(
skip: int = 0,
limit: int = 10,
search: Optional[str] = None
):
'''List files with optional query parameters'''
return db.list_files(skip=skip, limit=limit, search=search)
# Special cases (must come BEFORE generic routes)
@app.get("/files/special/recent")
async def get_recent_files():
'''Get recently updated files'''
return db.get_recent_files()Key Point: More specific routes must be defined BEFORE generic ones.
# v1 - Simple, required ID only
@app.get("/api/v1/items/{item_id}")
async def get_item_v1(item_id: int):
return {"item_id": item_id}
# v2 - Richer, with optional expansions
@app.get("/api/v2/items/{item_id}")
async def get_item_v2(
item_id: int,
expand: Optional[str] = Query(None, regex="^(user|reviews|related)$")
):
'''
Optional expand parameter to include related data.
/api/v2/items/123?expand=user ← Include user details
/api/v2/items/123?expand=reviews ← Include reviews
/api/v2/items/123 ← Basic info only
'''
item = db.get_item(item_id)
if expand == "user":
item["user"] = db.get_user(item["user_id"])
elif expand == "reviews":
item["reviews"] = db.get_reviews(item_id)
return itemfrom typing import Optional
# Single resource
@app.get("/users/{user_id}")
async def get_user(user_id: int):
return db.get_user(user_id)
# User with optional data
@app.get("/users/{user_id}/full")
async def get_user_full(user_id: int):
'''Get user with all related data'''
user = db.get_user(user_id)
user["posts"] = db.get_user_posts(user_id)
user["comments"] = db.get_user_comments(user_id)
user["followers"] = db.get_user_followers(user_id)
return user
# User timeline (alternative representation)
@app.get("/users/{user_id}/timeline")
async def get_user_timeline(user_id: int, limit: int = 20):
'''Get user's activity timeline'''
return db.get_timeline(user_id, limit=limit)# Level 1: Organization
@app.get("/orgs/{org_id}")
async def get_organization(org_id: int):
return db.get_org(org_id)
# Level 2: Team within organization
@app.get("/orgs/{org_id}/teams/{team_id}")
async def get_team(org_id: int, team_id: int):
team = db.get_team(team_id)
if team["org_id"] != org_id:
raise HTTPException(status_code=404)
return team
# Level 3: Member within team
@app.get("/orgs/{org_id}/teams/{team_id}/members/{member_id}")
async def get_member(org_id: int, team_id: int, member_id: int):
member = db.get_member(member_id)
if member["team_id"] != team_id or member["org_id"] != org_id:
raise HTTPException(status_code=404)
return member
# Also support shortcuts
@app.get("/teams/{team_id}/members/{member_id}")
async def get_member_direct(team_id: int, member_id: int):
'''Direct access without org_id if known'''
member = db.get_member(member_id)
if member["team_id"] != team_id:
raise HTTPException(status_code=404)
return memberfrom fastapi import HTTPException, status
@app.get("/resources/{resource_id}")
async def get_resource(resource_id: int):
'''Handle missing required parameters appropriately'''
# This code only runs if resource_id is a valid integer
resource = db.get_resource(resource_id)
if not resource:
# Resource ID was valid, but doesn't exist
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Resource {resource_id} not found"
)
return resource
# FastAPI automatically returns 422 if resource_id is invalid:
# /resources/abc → 422 Unprocessable Entity
# /resources/123 but doesn't exist → 404 Not Foundfrom fastapi import FastAPI, Query, Path
from typing import Optional
from pydantic import validator
app = FastAPI()
@app.get("/articles/{article_id}")
async def get_article(
# Required path parameter - validates type
article_id: int = Path(..., gt=0, description="Article ID (required)"),
# Optional query parameters - for filtering/options
include_comments: bool = Query(False),
include_author_details: bool = Query(False),
lang: Optional[str] = Query(None, regex="^[a-z]{2}$")
):
'''
Example of proper required vs optional handling.
Required: article_id (in path)
Optional: include_comments, include_author_details, lang (in query)
'''
article = db.get_article(article_id)
if include_comments:
article["comments"] = db.get_comments(article_id)
if include_author_details:
article["author"] = db.get_user(article["author_id"])
if lang:
article["translated_content"] = db.get_translation(article_id, lang)
return article| Scenario | Solution | Example |
|---|---|---|
| Always need identifier | Required path param | `/users/123` |
| Sometimes need extra data | Optional query param | `/users/123?include=posts` |
| Different operations | Multiple routes | `/users/{id}` vs `/users/search` |
| Alternative access method | Shortcut route | `/teams/{id}/members/{mid}` and `/members/{mid}` |
| Version differences | API versioning | `/v1/...` vs `/v2/...` |
# Cache optional expansions
from functools import lru_cache
@lru_cache(maxsize=128)
def get_user_with_cache(user_id: int):
return db.get_user(user_id)
@app.get("/users/{user_id}")
async def get_user(user_id: int, include_posts: bool = False):
user = get_user_with_cache(user_id)
if include_posts:
# Expensive operation only when requested
user["posts"] = db.get_user_posts(user_id)
return userChallenge: Design an API with at least 3 levels of required parameters and optional query filters.
Resources
Ojasa Mirai
Master AI-powered development skills through structured learning, real projects, and verified credentials. Whether you're upskilling your team or launching your career, we deliver the skills companies actually need.
Learn Deep • Build Real • Verify Skills • Launch Forward