Expert in FastAPI async endpoint patterns, dependency injection, request/response models, error handling, and CORS configuration. Use for all backend API implementations.
You are an expert in FastAPI, the modern, fast (high-performance) Python web framework for building APIs. This skill covers async patterns, dependency injection, request validation, and production-ready API design.
FastAPI = Speed + Type Safety + Automatic Documentation
async def for concurrency✅ Use this skill for:
❌ Don't use for:
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
# Create app instance
app = FastAPI(
title="Todo API",
description="Full-stack todo application API",
version="1.0.0"
)
# Configure CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"], # Frontend URL
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Health check endpoint
@app.get("/health")
async def health_check():
return {"status": "healthy"}
Key Concepts:
FastAPI() creates application instance with metadataallow_credentials=True for cookie/auth headersfrom fastapi import FastAPI, HTTPException
from typing import List
@app.get("/todos", response_model=List[TodoPublic])
async def get_todos(
completed: bool | None = None,
skip: int = 0,
limit: int = 100
):
"""
Get all todos with optional filtering.
- **completed**: Filter by completion status
- **skip**: Number of records to skip (pagination)
- **limit**: Maximum number of records to return
"""
# Endpoint logic here
return todos
Key Concepts:
async def for endpointscompleted: bool | None)response_model for response validation and documentationfrom fastapi import Depends
from sqlmodel.ext.asyncio.session import AsyncSession
# Database session dependency
async def get_session() -> AsyncSession:
async with async_session() as session:
yield session
# Use dependency in endpoint
@app.get("/todos")
async def get_todos(
session: AsyncSession = Depends(get_session)
):
# session is automatically provided
statement = select(Todo)
result = await session.exec(statement)
return result.all()
Key Concepts:
Depends() injects dependencies automaticallyfrom pydantic import BaseModel, Field
from typing import Optional
from datetime import datetime
# Request model (for creating)
class TodoCreate(BaseModel):
title: str = Field(..., min_length=1, max_length=200)
description: Optional[str] = Field(None, max_length=1000)
completed: bool = False
# Request model (for updating)
class TodoUpdate(BaseModel):
title: Optional[str] = Field(None, min_length=1, max_length=200)
description: Optional[str] = None
completed: Optional[bool] = None
# Response model (for returning)
class TodoPublic(BaseModel):
id: str
title: str
description: Optional[str]
completed: bool
user_id: str
created_at: datetime
class Config:
from_attributes = True # Allow ORM mode
# Use in endpoint
@app.post("/todos", response_model=TodoPublic, status_code=201)
async def create_todo(todo: TodoCreate):
# todo is automatically validated
# Return value is automatically validated against TodoPublic
return new_todo
Key Concepts:
Field() for validation constraintsresponse_model excludes internal fieldsfrom_attributes=True for SQLModel compatibilityfrom fastapi import HTTPException, status
@app.get("/todos/{todo_id}")
async def get_todo(todo_id: str):
# Query database
todo = await fetch_todo(todo_id)
if not todo:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Todo not found"
)
return todo
# Custom exception handler
from fastapi.responses import JSONResponse
from sqlalchemy.exc import IntegrityError
@app.exception_handler(IntegrityError)
async def integrity_error_handler(request, exc):
return JSONResponse(
status_code=status.HTTP_400_BAD_REQUEST,
content={"detail": "Database constraint violation"}
)
Key Concepts:
raise HTTPException() for expected errorsstatus module for status codesfrom fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from jose import jwt, JWTError
import os
# Security scheme
security = HTTPBearer()
# JWT verification dependency
async def get_current_user(
credentials: HTTPAuthorizationCredentials = Depends(security),
session: AsyncSession = Depends(get_session)
) -> User:
"""
Verify JWT token and return current user.
Raises 401 if token is invalid.
"""
token = credentials.credentials
try:
# Decode JWT
payload = jwt.decode(
token,
os.environ.get("BETTER_AUTH_SECRET"),
algorithms=["HS256"]
)
# Extract user_id from token
user_id: str = payload.get("sub")
if user_id is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials"
)
except JWTError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials"
)
# Get user from database
statement = select(User).where(User.id == user_id)
result = await session.exec(statement)
user = result.first()
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="User not found"
)
return user
# Protected endpoint
@app.get("/todos")
async def get_todos(
current_user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_session)
):
# current_user is automatically provided and verified
statement = select(Todo).where(Todo.user_id == current_user.id)
result = await session.exec(statement)
return result.all()
Key Concepts:
HTTPBearer extracts token from Authorization headerDepends() chains dependencies (get_current_user uses get_session)from fastapi import APIRouter
# Create router
router = APIRouter(
prefix="/todos",
tags=["todos"],
dependencies=[Depends(get_current_user)] # Apply to all routes
)
# Define routes on router
@router.get("/", response_model=List[TodoPublic])
async def get_todos(
current_user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_session)
):
return todos
@router.post("/", response_model=TodoPublic, status_code=201)
async def create_todo(
todo: TodoCreate,
current_user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_session)
):
return new_todo
# Include router in main app
app.include_router(router)
Key Concepts:
APIRouter groups related endpointsprefix adds base path to all routestags organizes docs sectionsdependencies applies to all routes in router@router.method()from fastapi import APIRouter, Depends, HTTPException, status
from sqlmodel import select
from sqlmodel.ext.asyncio.session import AsyncSession
from typing import List
from uuid import uuid4
router = APIRouter(prefix="/todos", tags=["todos"])
# CREATE
@router.post("/", response_model=TodoPublic, status_code=status.HTTP_201_CREATED)
async def create_todo(
todo: TodoCreate,
session: AsyncSession = Depends(get_session),
current_user: User = Depends(get_current_user)
):
db_todo = Todo(
id=str(uuid4()),
**todo.dict(),
user_id=current_user.id
)
session.add(db_todo)
await session.commit()
await session.refresh(db_todo)
return db_todo
# READ (all)
@router.get("/", response_model=List[TodoPublic])
async def get_todos(
completed: bool | None = None,
session: AsyncSession = Depends(get_session),
current_user: User = Depends(get_current_user)
):
statement = select(Todo).where(Todo.user_id == current_user.id)
if completed is not None:
statement = statement.where(Todo.completed == completed)
result = await session.exec(statement)
return result.all()
# READ (single)
@router.get("/{todo_id}", response_model=TodoPublic)
async def get_todo(
todo_id: str,
session: AsyncSession = Depends(get_session),
current_user: User = Depends(get_current_user)
):
statement = select(Todo).where(
Todo.id == todo_id,
Todo.user_id == current_user.id
)
result = await session.exec(statement)
todo = result.first()
if not todo:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Todo not found"
)
return todo
# UPDATE
@router.patch("/{todo_id}", response_model=TodoPublic)
async def update_todo(
todo_id: str,
todo_update: TodoUpdate,
session: AsyncSession = Depends(get_session),
current_user: User = Depends(get_current_user)
):
statement = select(Todo).where(
Todo.id == todo_id,
Todo.user_id == current_user.id
)
result = await session.exec(statement)
db_todo = result.first()
if not db_todo:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Todo not found"
)
# Update only provided fields
update_data = todo_update.dict(exclude_unset=True)
for key, value in update_data.items():
setattr(db_todo, key, value)
session.add(db_todo)
await session.commit()
await session.refresh(db_todo)
return db_todo
# DELETE
@router.delete("/{todo_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_todo(
todo_id: str,
session: AsyncSession = Depends(get_session),
current_user: User = Depends(get_current_user)
):
statement = select(Todo).where(
Todo.id == todo_id,
Todo.user_id == current_user.id
)
result = await session.exec(statement)
todo = result.first()
if not todo:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Todo not found"
)
await session.delete(todo)
await session.commit()
return None
from fastapi.middleware.cors import CORSMiddleware
# Development
origins = [
"http://localhost:3000",
"http://127.0.0.1:3000",
]
# Production (add deployed frontend URL)
if os.environ.get("ENVIRONMENT") == "production":
origins.append("https://your-app.vercel.app")
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE"],
allow_headers=["*"],
)
Key Concepts:
allow_credentials=True for cookies/authallow_headers=["*"] for auth headersfrom pydantic import BaseModel, Field, validator
from typing import Optional
class TodoCreate(BaseModel):
title: str = Field(..., min_length=1, max_length=200)
description: Optional[str] = Field(None, max_length=1000)
priority: int = Field(default=3, ge=1, le=5)
@validator('title')
def title_must_not_be_empty(cls, v):
if not v.strip():
raise ValueError('Title cannot be empty or whitespace')
return v.strip()
@validator('priority')
def priority_must_be_valid(cls, v):
if v not in [1, 2, 3, 4, 5]:
raise ValueError('Priority must be between 1 and 5')
return v
Key Concepts:
Field() for basic validation@validator for custom validationfrom fastapi import BackgroundTasks
def send_notification(email: str, message: str):
# Send email (example)
print(f"Sending to {email}: {message}")
@router.post("/todos/", response_model=TodoPublic)
async def create_todo(
todo: TodoCreate,
background_tasks: BackgroundTasks,
current_user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_session)
):
# Create todo
db_todo = Todo(**todo.dict(), user_id=current_user.id)
session.add(db_todo)
await session.commit()
# Queue background task
background_tasks.add_task(
send_notification,
current_user.email,
f"Todo created: {todo.title}"
)
return db_todo
Key Concepts:
BackgroundTasks for async operations after responseUse the using-context7 skill to query for:
✅ "FastAPI dependency injection best practices"
✅ "FastAPI async database session management"
✅ "FastAPI JWT authentication with python-jose"
✅ "FastAPI exception handlers for custom errors"
✅ "FastAPI CORS configuration for production"
✅ "FastAPI response model exclude fields"
Don't query for:
❌ HTTP status codes (200, 201, 404, 500)
❌ REST principles (GET, POST, PUT, DELETE)
❌ Python async/await syntax
❌ Basic Pydantic models
from fastapi.testclient import TestClient
import pytest
@pytest.fixture
def client():
return TestClient(app)
def test_create_todo(client):
response = client.post(
"/todos/",
json={"title": "Test Todo", "completed": False},
headers={"Authorization": "Bearer test-token"}
)
assert response.status_code == 201
assert response.json()["title"] == "Test Todo"
@pytest.mark.asyncio
async def test_get_todos(async_client):
response = await async_client.get(
"/todos/",
headers={"Authorization": "Bearer test-token"}
)
assert response.status_code == 200
assert isinstance(response.json(), list)
@router.get("/todos/")
async def get_todos(
skip: int = 0,
limit: int = 100,
completed: bool | None = None,
search: str | None = None
):
# Parameters automatically extracted from URL
pass
@router.get("/todos/{todo_id}")
async def get_todo(todo_id: str):
# todo_id extracted from URL path
pass
@router.post("/todos/")
async def create_todo(todo: TodoCreate):
# todo parsed from JSON body
pass
from fastapi import Header
@router.get("/todos/")
async def get_todos(user_agent: str = Header(None)):
# user_agent from User-Agent header
pass
# Perfect integration
@router.get("/todos/", response_model=List[TodoPublic])
async def get_todos(
session: AsyncSession = Depends(get_session),
current_user: User = Depends(get_current_user)
):
statement = select(Todo).where(Todo.user_id == current_user.id)
result = await session.exec(statement)
todos = result.all()
return todos # Automatically serialized
Key Concepts:
from_attributes=Truereference.md - Quick reference for common patternsexamples.md - Real Phase 2 API implementationsasync def, await for I/OThis skill provides the foundation for all backend API operations in Phase 2. Combine it with sqlmodel-database for complete CRUD implementations and better-auth-jwt for authentication.