FastAPI Developer Guide
Section titled “FastAPI Developer Guide”FastAPI is a Python framework for building APIs with standard type hints. Typed route parameters and Pydantic models provide request validation, serialization, and generated OpenAPI documentation.
Getting Started
Section titled “Getting Started”Installation
Section titled “Installation”Create a virtual environment and install FastAPI with its standard development server dependencies:
python -m venv .venv
# macOS/Linuxsource .venv/bin/activate
# PowerShell.\.venv\Scripts\Activate.ps1
pip install "fastapi[standard]"First Application
Section titled “First Application”Create main.py:
from fastapi import FastAPI
app = FastAPI(title="Store API")
@app.get("/")async def root() -> dict[str, str]: return {"message": "Hello, FastAPI"}Run the local development server:
fastapi dev main.pyUseful local URLs:
http://127.0.0.1:8000/ # API routehttp://127.0.0.1:8000/docs # Swagger UIhttp://127.0.0.1:8000/redoc # ReDoc documentationhttp://127.0.0.1:8000/openapi.jsonPath Operations
Section titled “Path Operations”Common HTTP Methods
Section titled “Common HTTP Methods”from fastapi import FastAPI, status
app = FastAPI()
@app.get("/items")async def list_items(): return []
@app.post("/items", status_code=status.HTTP_201_CREATED)async def create_item(): return {"created": True}
@app.put("/items/{item_id}")async def replace_item(item_id: int): return {"id": item_id}
@app.patch("/items/{item_id}")async def update_item(item_id: int): return {"id": item_id}
@app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT)async def delete_item(item_id: int): return NonePath and Query Parameters
Section titled “Path and Query Parameters”Parameters named in the route path become path parameters. Other simple typed parameters become query parameters:
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/{item_id}")async def get_item( item_id: int, search: str | None = None, limit: int = Query(default=20, ge=1, le=100), offset: int = Query(default=0, ge=0),): return { "id": item_id, "search": search, "limit": limit, "offset": offset, }FastAPI rejects values that do not satisfy the declared types or constraints and documents the accepted inputs in OpenAPI.
Request Bodies and Responses
Section titled “Request Bodies and Responses”Pydantic Models
Section titled “Pydantic Models”Declare JSON request bodies with BaseModel:
from pydantic import BaseModel, Field
class ItemCreate(BaseModel): name: str = Field(min_length=1, max_length=100) price: float = Field(gt=0) description: str | None = None in_stock: bool = Truefrom fastapi import FastAPI, status
app = FastAPI()
@app.post("/items", status_code=status.HTTP_201_CREATED)async def create_item(item: ItemCreate): return itemResponse Models
Section titled “Response Models”Keep input and public output models separate so internal fields are not exposed:
from pydantic import BaseModel
class UserCreate(BaseModel): email: str password: str
class UserPublic(BaseModel): id: int email: str
@app.post("/users", response_model=UserPublic, status_code=201)async def create_user(user: UserCreate): return {"id": 1, "email": user.email, "password": "not-returned"}The response_model filters and validates data returned to the client.
Status Codes and Errors
Section titled “Status Codes and Errors”from fastapi import HTTPException, status
items = {1: {"id": 1, "name": "Keyboard"}}
@app.get("/items/{item_id}")async def get_item(item_id: int): item = items.get(item_id) if item is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Item not found", ) return itemHeaders, Cookies, and Files
Section titled “Headers, Cookies, and Files”from typing import Annotated
from fastapi import Cookie, File, Header, UploadFile
@app.get("/request-info")async def request_info( user_agent: Annotated[str | None, Header()] = None, session_id: Annotated[str | None, Cookie()] = None,): return {"user_agent": user_agent, "session_id": session_id}
@app.post("/uploads")async def upload(file: UploadFile = File(...)): content = await file.read() return {"filename": file.filename, "size": len(content)}Install multipart support when accepting forms or uploaded files:
pip install python-multipartOrganizing an Application
Section titled “Organizing an Application”Suggested Layout
Section titled “Suggested Layout”app/ __init__.py main.py dependencies.py models.py schemas.py routers/ __init__.py items.pytests/ test_items.pyUse an API Router
Section titled “Use an API Router”from fastapi import APIRouter, HTTPException
router = APIRouter(prefix="/items", tags=["items"])
@router.get("/{item_id}")async def get_item(item_id: int): if item_id < 1: raise HTTPException(status_code=404, detail="Item not found") return {"id": item_id}from fastapi import FastAPI
from .routers import items
app = FastAPI(title="Store API", version="1.0.0")app.include_router(items.router)fastapi dev app/main.pyDependencies
Section titled “Dependencies”Shared Request Logic
Section titled “Shared Request Logic”Dependencies are reusable callables for authorization, pagination, service objects, and database sessions:
from typing import Annotated
from fastapi import Depends, Query
def pagination( limit: int = Query(default=20, ge=1, le=100), offset: int = Query(default=0, ge=0),) -> dict[str, int]: return {"limit": limit, "offset": offset}
@app.get("/items")async def list_items(page: Annotated[dict[str, int], Depends(pagination)]): return pageDependencies With Cleanup
Section titled “Dependencies With Cleanup”Use yield when a resource must be closed after a request:
from collections.abc import Generator
def get_db() -> Generator[str, None, None]: connection = "database-connection" try: yield connection finally: pass # Close a real database session here.from typing import Annotated
from fastapi import Depends
@app.get("/reports")async def reports(db: Annotated[str, Depends(get_db)]): return {"connection": db}Database Pattern With SQLAlchemy
Section titled “Database Pattern With SQLAlchemy”Session Dependency
Section titled “Session Dependency”from collections.abc import Generator
from sqlalchemy import create_enginefrom sqlalchemy.orm import Session, sessionmaker
DATABASE_URL = "sqlite:///./app.db"
engine = create_engine( DATABASE_URL, connect_args={"check_same_thread": False},)SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False)
def get_db() -> Generator[Session, None, None]: db = SessionLocal() try: yield db finally: db.close()CRUD Endpoint Pattern
Section titled “CRUD Endpoint Pattern”from typing import Annotated
from fastapi import Depends, HTTPExceptionfrom pydantic import BaseModelfrom sqlalchemy.orm import Session
class ItemPublic(BaseModel): id: int name: str
@app.get("/items/{item_id}", response_model=ItemPublic)def read_item(item_id: int, db: Annotated[Session, Depends(get_db)]): item = db.get(Item, item_id) if item is None: raise HTTPException(status_code=404, detail="Item not found") return itemKeep database table models separate from request and response schema models. Apply schema changes through migrations rather than recreating tables in production.
Authentication and Authorization
Section titled “Authentication and Authorization”Bearer Token Dependency
Section titled “Bearer Token Dependency”from typing import Annotated
from fastapi import Depends, HTTPException, statusfrom fastapi.security import OAuth2PasswordBearer
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
async def current_user(token: Annotated[str, Depends(oauth2_scheme)]): if token != "example-token": raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid authentication credentials", headers={"WWW-Authenticate": "Bearer"}, ) return {"username": "demo"}
@app.get("/users/me")async def read_me(user: Annotated[dict, Depends(current_user)]): return userThis demonstrates dependency wiring only. Real applications should hash passwords, validate signed or stored tokens, set expiration rules, and load users from a trusted store.
Router-Level Protection
Section titled “Router-Level Protection”from fastapi import APIRouter, Depends
admin_router = APIRouter( prefix="/admin", dependencies=[Depends(current_user)],)
@admin_router.get("/stats")async def stats(): return {"active_users": 10}Middleware and CORS
Section titled “Middleware and CORS”Allow browser clients only from intended origins:
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware( CORSMiddleware, allow_origins=["https://app.example.com"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"],)Avoid allow_origins=["*"] when credentials are allowed or when the API should not be publicly callable from arbitrary browser origins.
Background Tasks and Lifespan
Section titled “Background Tasks and Lifespan”Small After-Response Tasks
Section titled “Small After-Response Tasks”from fastapi import BackgroundTasks
def write_notification(email: str) -> None: print(f"Notify {email}")
@app.post("/notifications")async def notify(email: str, tasks: BackgroundTasks): tasks.add_task(write_notification, email) return {"message": "Notification scheduled"}Use BackgroundTasks for short work in the same process. For durable or CPU-heavy jobs, use a job queue so work can retry and continue if the API process restarts.
Application Startup and Shutdown
Section titled “Application Startup and Shutdown”from contextlib import asynccontextmanager
from fastapi import FastAPI
@asynccontextmanagerasync def lifespan(app: FastAPI): app.state.ready = True yield app.state.ready = False
app = FastAPI(lifespan=lifespan)Testing
Section titled “Testing”Test an Endpoint
Section titled “Test an Endpoint”Install testing dependencies:
pip install pytest httpxfrom fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_root(): response = client.get("/")
assert response.status_code == 200 assert response.json() == {"message": "Hello, FastAPI"}pytestpytest -qOverride Dependencies
Section titled “Override Dependencies”Replace authentication or databases during tests:
from fastapi.testclient import TestClient
from app.main import app, current_user
async def fake_user(): return {"username": "tester"}
app.dependency_overrides[current_user] = fake_userclient = TestClient(app)
def test_profile(): response = client.get("/users/me") assert response.status_code == 200 assert response.json()["username"] == "tester"
def teardown_module(): app.dependency_overrides.clear()Production and Deployment
Section titled “Production and Deployment”Configuration From Environment
Section titled “Configuration From Environment”Keep secrets and environment-specific values out of committed source:
import os
DATABASE_URL = os.environ["DATABASE_URL"]SECRET_KEY = os.environ["SECRET_KEY"]Run for Production
Section titled “Run for Production”fastapi run app/main.pyDevelopment reload mode is useful locally, but it should not be used as the production process strategy. Deploy behind HTTPS, configure allowed hosts and CORS intentionally, and ensure database migrations run as part of release operations.
Container Example
Section titled “Container Example”FROM python:3.12-slim
WORKDIR /codeCOPY requirements.txt .RUN pip install --no-cache-dir -r requirements.txt
COPY app ./appCMD ["fastapi", "run", "app/main.py", "--port", "8000"]Common Mistakes
Section titled “Common Mistakes”| Problem | Better approach |
|---|---|
| Returning passwords or private fields | Use a dedicated response_model |
Blocking network or disk work inside async def | Use async libraries or run synchronous work appropriately |
Putting every route in main.py | Split endpoints into APIRouter modules |
| Creating a database session without cleanup | Provide it through a yield dependency |
| Trusting tokens without verification | Validate credentials and authorization server-side |
| Using in-process background tasks for critical work | Use a durable task queue |
Quick Reference
Section titled “Quick Reference”pip install "fastapi[standard]" # Install framework and common server toolingfastapi dev main.py # Run development server with reloadfastapi run app/main.py # Run production-mode serverpytest # Run testsfrom fastapi import Depends, FastAPI, HTTPExceptionfrom fastapi.testclient import TestClientfrom pydantic import BaseModel, Field
app = FastAPI()
@app.get("/resource/{resource_id}") # Read@app.post("/resource", status_code=201) # Create@app.put("/resource/{resource_id}") # Replace@app.patch("/resource/{resource_id}") # Partially update@app.delete("/resource/{resource_id}") # Delete