FastAPI
Complete Reference Guide — Modern Python Web APIs
FastAPI is a high-performance Python web framework for building APIs. It is based on standard Python type hints, and
achieves speeds comparable to NodeJS and Go. This guide covers everything from installation to production
deployment.
Topics Covered
Installation & project setup
First API & running the server
Path parameters & query parameters
Request body & Pydantic models
Response models & status codes
Dependency injection
Authentication (OAuth2 / JWT / API keys)
Routers & application structure
Middleware & CORS
Background tasks & async
Database integration (SQLAlchemy / async)
File uploads & static files
Testing with pytest
Deployment (Docker / Uvicorn / Gunicorn)
Common options, tips & troubleshooting
1. What Is FastAPI?
FastAPI is a modern, fast web framework for building APIs with Python, based on ASGI (Asynchronous Server Gateway
Interface). It leverages Python type hints to automatically validate data, generate documentation, and provide IDE
autocompletion.
1.1 Key Features
Feature Detail
Performance On par with NodeJS & Go — powered by Starlette & Pydantic
Auto docs Swagger UI (/docs) and ReDoc (/redoc) generated automatically
Type safety Python type hints drive validation, serialization, IDE support
Async support First-class async/await — handles thousands of concurrent requests
Standards-based OpenAPI 3.x + JSON Schema — interoperable with any tooling
Dependency injection Clean, testable DI system built into the framework
1.2 FastAPI vs Flask vs Django REST
Aspect FastAPI Flask Django REST
Speed Very fast (ASGI) Moderate (WSGI) Moderate (WSGI)
Async Native Via extensions Via channels
Auto docs Built-in Manual/extension drf-spectacular
Validation Pydantic (auto) Manual/Marshmallow Serializers
Learning curve Low-Medium Low Medium-High
Best for APIs, microservices Simple APIs, apps Full-stack apps
2. Installation & Project Setup
2.1 Install FastAPI
# Minimal install
pip install fastapi
# With Uvicorn ASGI server (recommended)
pip install 'fastapi[standard]'
# Includes: uvicorn, pydantic, email-validator, python-multipart, etc.
# Or install components separately
pip install fastapi uvicorn[standard] pydantic
2.2 Recommended Project Layout
myproject/
app/
__init__.py
[Link] # FastAPI app instance + startup
[Link] # Settings / env vars
[Link] # Shared DI dependencies
models/
__init__.py
[Link] # SQLAlchemy models
schemas/
__init__.py
[Link] # Pydantic schemas
routers/
__init__.py
[Link] # APIRouter for /users
[Link]
services/
user_service.py
tests/
test_users.py
[Link]
Dockerfile
2.3 Virtual Environment (best practice)
python -m venv .venv
source .venv/bin/activate # Linux/macOS
.venv\Scripts\activate # Windows
pip install 'fastapi[standard]'
3. Your First FastAPI App
3.1 Minimal App ([Link])
from fastapi import FastAPI
app = FastAPI(
title='My API',
description='A sample FastAPI application',
version='1.0.0',
)
@[Link]('/')
def read_root():
return {'message': 'Hello, FastAPI!'}
@[Link]('/items/{item_id}')
def read_item(item_id: int, q: str | None = None):
return {'item_id': item_id, 'q': q}
3.2 Running the Server
# Development (auto-reload on file changes)
uvicorn [Link]:app --reload
# Custom host/port
uvicorn [Link]:app --host [Link] --port 8080 --reload
# Or run from Python
import uvicorn
if __name__ == '__main__':
[Link]('[Link]:app', host='[Link]', port=8000, reload=True)
3.3 Auto-Generated Docs
URL Tool Description
[Link] Swagger UI Interactive API explorer — try endpoints live
[Link] ReDoc Clean reference documentation
[Link] OpenAPI Raw OpenAPI 3.x JSON schema
4. Path & Query Parameters
4.1 Path Parameters
from fastapi import FastAPI, Path
app = FastAPI()
@[Link]('/users/{user_id}')
def get_user(user_id: int): # type enforced automatically
return {'user_id': user_id}
# With validation constraints
@[Link]('/items/{item_id}')
def get_item(
item_id: int = Path(gt=0, le=1000, description='Item ID 1-1000'),
):
return {'item_id': item_id}
4.2 Query Parameters
from fastapi import Query
@[Link]('/items/')
def list_items(
skip: int = 0,
limit: int = Query(default=10, ge=1, le=100),
search: str | None = Query(default=None, min_length=3),
):
return {'skip': skip, 'limit': limit, 'search': search}
# GET /items/?skip=0&limit;=20&search;=foo
4.3 Enum Path Parameters
from enum import Enum
class ModelName(str, Enum):
alexnet = 'alexnet'
resnet = 'resnet'
lenet = 'lenet'
@[Link]('/models/{model_name}')
def get_model(model_name: ModelName):
return {'model': model_name, 'value': model_name.value}
✓ FastAPI automatically returns 422 Unprocessable Entity with detailed errors when parameters fail validation.
5. Request Body & Pydantic Models
5.1 Basic Pydantic Schema
from pydantic import BaseModel, Field, EmailStr
from typing import Optional
from datetime import datetime
class ItemCreate(BaseModel):
name: str = Field(min_length=1, max_length=100)
description: Optional[str] = Field(default=None, max_length=500)
price: float = Field(gt=0, description='Price must be positive')
tax: float | None = None
tags: list[str] = []
class UserCreate(BaseModel):
email: EmailStr
username: str = Field(min_length=3, pattern=r'^[a-z0-9_]+$')
full_name: str | None = None
created_at: datetime = Field(default_factory=[Link])
class Config:
json_schema_extra = {
'example': {'email': 'user@[Link]', 'username': 'jdoe'}
}
5.2 Using Models in Endpoints
@[Link]('/items/', status_code=201)
def create_item(item: ItemCreate):
# item is fully validated, typed, and IDE-friendly
item_dict = item.model_dump() # Pydantic v2
item_dict['id'] = 1 # simulate DB insert
return item_dict
# Mixed: path + body + query
@[Link]('/items/{item_id}')
def update_item(item_id: int, item: ItemCreate, notify: bool = False):
return {'item_id': item_id, 'item': item, 'notify': notify}
5.3 Nested Models
class Address(BaseModel):
street: str
city: str
country: str = 'US'
class UserFull(BaseModel):
username: str
address: Address
aliases: list[str] = []
5.4 Pydantic Field Types Quick Reference
Type Import / Usage Notes
str, int, float Built-in Standard Python types
EmailStr pydantic[email] Validates email format
HttpUrl from pydantic import HttpUrl Validates URL
UUID from uuid import UUID UUID v4 supported
datetime / date from datetime import ... ISO 8601 auto-parsed
list[T] Built-in Typed lists
dict[str, T] Built-in Typed dicts
Literal['a','b'] from typing import Literal Fixed value set
Optional[T] from typing import Optional Same as T | None
6. Response Models & Status Codes
6.1 Response Model
from pydantic import BaseModel
from fastapi import FastAPI
from [Link] import JSONResponse
class UserOut(BaseModel): # only expose safe fields
id: int
username: str
email: str
# password is NOT here — never returned
class UserIn(UserOut):
password: str # extends UserOut for input
@[Link]('/users/', response_model=UserOut, status_code=201)
def create_user(user: UserIn):
# FastAPI strips any field not in UserOut automatically
return user
6.2 HTTP Status Codes
Code Constant Use
200 HTTP_200_OK Default success
201 HTTP_201_CREATED Resource created (POST)
204 HTTP_204_NO_CONTENT Delete / no body
400 HTTP_400_BAD_REQUEST Invalid input
401 HTTP_401_UNAUTHORIZED Not authenticated
403 HTTP_403_FORBIDDEN Authenticated but no permission
404 HTTP_404_NOT_FOUND Resource not found
409 HTTP_409_CONFLICT Duplicate resource
422 HTTP_422_UNPROCESSABLE Validation error (auto)
500 HTTP_500_INTERNAL_SERVER_ERROR Unexpected server error
6.3 Raising HTTP Exceptions
from fastapi import HTTPException, status
@[Link]('/users/{user_id}')
def get_user(user_id: int):
user = [Link](user_id) # hypothetical DB call
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f'User {user_id} not found',
)
return user
7. Dependency Injection
FastAPI's DI system lets you declare shared logic (auth checks, DB sessions, pagination params) as reusable
dependencies injected automatically.
7.1 Simple Function Dependency
from fastapi import Depends
def common_pagination(skip: int = 0, limit: int = 20):
return {'skip': skip, 'limit': limit}
@[Link]('/items/')
def list_items(pagination: dict = Depends(common_pagination)):
return {'pagination': pagination}
7.2 Class-Based Dependency
class DBSession:
def __init__(self):
[Link] = get_database_connection() # your DB factory
def __del__(self):
[Link]()
@[Link]('/users/')
def list_users(session: DBSession = Depends()):
return [Link].query_all_users()
7.3 Dependency with Yield (context manager style)
from [Link] import Session
from [Link] import SessionLocal
def get_db():
db = SessionLocal()
try:
yield db # hand DB session to endpoint
finally:
[Link]() # always runs — even on exception
@[Link]('/users/{uid}')
def get_user(uid: int, db: Session = Depends(get_db)):
return [Link](User).filter([Link] == uid).first()
7.4 Dependency Chaining
def verify_token(token: str = Depends(oauth2_scheme)):
... # raises 401 if invalid
return payload
def get_current_user(payload = Depends(verify_token), db = Depends(get_db)):
return [Link](User).filter([Link] == payload['sub']).first()
@[Link]('/me')
def me(user = Depends(get_current_user)):
return user
✓ Use Annotated[T, Depends(...)] (Python 3.11+) for cleaner signatures.
8. Authentication
8.1 OAuth2 Password Flow + JWT
pip install python-jose[cryptography] passlib[bcrypt]
from [Link] import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from [Link] import CryptContext
from datetime import datetime, timedelta
SECRET_KEY = 'your-secret-key-change-in-production'
ALGORITHM = 'HS256'
ACCESS_TOKEN_EXPIRE_MINUTES = 30
pwd_context = CryptContext(schemes=['bcrypt'], deprecated='auto')
oauth2_scheme = OAuth2PasswordBearer(tokenUrl='/auth/token')
def create_access_token(data: dict, expires_delta: timedelta | None = None):
to_encode = [Link]()
expire = [Link]() + (expires_delta or timedelta(minutes=15))
to_encode.update({'exp': expire})
return [Link](to_encode, SECRET_KEY, algorithm=ALGORITHM)
@[Link]('/auth/token')
def login(form_data: OAuth2PasswordRequestForm = Depends()):
user = authenticate_user(form_data.username, form_data.password)
if not user:
raise HTTPException(status_code=401, detail='Invalid credentials')
token = create_access_token({'sub': [Link]},
timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES))
return {'access_token': token, 'token_type': 'bearer'}
8.2 API Key Authentication
from [Link] import APIKeyHeader
from fastapi import Security
api_key_header = APIKeyHeader(name='X-API-Key')
def require_api_key(api_key: str = Security(api_key_header)):
if api_key != settings.API_KEY:
raise HTTPException(status_code=403, detail='Invalid API key')
return api_key
@[Link]('/protected')
def protected_route(key = Depends(require_api_key)):
return {'status': 'authorized'}
✕ Never hardcode secrets. Use environment variables and a secrets manager in production.
9. Routers & Application Structure
9.1 Creating a Router (app/routers/[Link])
from fastapi import APIRouter, Depends, HTTPException, status
from [Link] import UserOut, UserCreate
from [Link] import get_db, get_current_user
router = APIRouter(
prefix='/users',
tags=['users'],
dependencies=[Depends(get_current_user)], # auth on all routes
responses={404: {'description': 'Not found'}},
)
@[Link]('/', response_model=list[UserOut])
def list_users(db = Depends(get_db)):
return [Link](User).all()
@[Link]('/{user_id}', response_model=UserOut)
def get_user(user_id: int, db = Depends(get_db)):
user = [Link](User).filter([Link] == user_id).first()
if not user:
raise HTTPException(status_code=404, detail='User not found')
return user
@[Link]('/', response_model=UserOut, status_code=201)
def create_user(user_in: UserCreate, db = Depends(get_db)):
...
@[Link]('/{user_id}', status_code=204)
def delete_user(user_id: int, db = Depends(get_db)):
...
9.2 Mounting Routers (app/[Link])
from fastapi import FastAPI
from [Link] import users, items, auth
app = FastAPI()
app.include_router([Link])
app.include_router([Link])
app.include_router([Link], prefix='/v1', tags=['items-v1'])
10. Middleware & CORS
10.1 CORS (Cross-Origin Resource Sharing)
from [Link] import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=['[Link] # list of allowed origins
allow_origin_regex=r'[Link]
allow_credentials=True,
allow_methods=['*'],
allow_headers=['*'],
max_age=3600,
)
■ Use specific origins in production — never allow_origins=['*'] with credentials.
10.2 Custom Middleware
import time
from fastapi import Request
@[Link]('http')
async def add_process_time_header(request: Request, call_next):
start = time.perf_counter()
response = await call_next(request)
duration = time.perf_counter() - start
[Link]['X-Process-Time'] = str(round(duration, 4))
return response
10.3 Trusted Host / GZip / HTTPs Redirect
from [Link] import TrustedHostMiddleware
from [Link] import GZipMiddleware
from [Link] import HTTPSRedirectMiddleware
app.add_middleware(TrustedHostMiddleware, allowed_hosts=['[Link]', '*.[Link]'])
app.add_middleware(GZipMiddleware, minimum_size=1000)
app.add_middleware(HTTPSRedirectMiddleware) # prod only
11. Async, Background Tasks & Lifespan
11.1 Async Endpoints
import httpx
# Use async def for I/O-bound work (DB calls, HTTP, files)
@[Link]('/external-data')
async def fetch_data():
async with [Link]() as client:
resp = await [Link]('[Link]
return [Link]()
# Use def for CPU-bound work (FastAPI runs it in a thread pool)
@[Link]('/compute')
def heavy_compute(data: dict):
result = run_heavy_ml_model(data) # blocking OK in def
return result
11.2 Background Tasks
from fastapi import BackgroundTasks
def send_welcome_email(email: str):
# runs after response is sent
send_email(email, subject='Welcome!')
@[Link]('/users/', status_code=201)
def create_user(user: UserCreate, bg: BackgroundTasks):
new_user = db_create_user(user)
bg.add_task(send_welcome_email, [Link])
return new_user # response returned immediately
11.3 Lifespan Events (startup / shutdown)
from contextlib import asynccontextmanager
from fastapi import FastAPI
@asynccontextmanager
async def lifespan(app: FastAPI):
# startup: runs before accepting requests
await [Link]()
load_ml_model()
yield # app runs here
# shutdown: runs after last request
await [Link]()
app = FastAPI(lifespan=lifespan)
12. Database Integration
12.1 SQLAlchemy (sync)
pip install sqlalchemy psycopg2-binary # PostgreSQL
pip install sqlalchemy aiosqlite # SQLite async
# app/[Link]
from sqlalchemy import create_engine
from [Link] import sessionmaker, DeclarativeBase
DATABASE_URL = 'postgresql://user:pass@localhost/mydb'
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
class Base(DeclarativeBase):
pass
# app/models/[Link]
from sqlalchemy import Column, Integer, String
from [Link] import Base
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True, index=True)
email = Column(String, unique=True, index=True)
username = Column(String, unique=True)
hashed_password = Column(String)
12.2 Async SQLAlchemy
pip install sqlalchemy[asyncio] asyncpg
from [Link] import create_async_engine, AsyncSession
from [Link] import sessionmaker
DATABASE_URL = 'postgresql+asyncpg://user:pass@localhost/mydb'
engine = create_async_engine(DATABASE_URL, echo=True)
AsyncSessionLocal = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
async def get_db():
async with AsyncSessionLocal() as session:
yield session
13. File Uploads & Static Files
13.1 File Upload
from fastapi import UploadFile, File
import aiofiles
@[Link]('/upload/')
async def upload_file(file: UploadFile = File(...)):
contents = await [Link]()
# Save to disk async:
async with [Link](f'uploads/{[Link]}', 'wb') as f:
await [Link](contents)
return {'filename': [Link], 'size': len(contents)}
# Multiple files:
@[Link]('/upload-many/')
async def upload_multiple(files: list[UploadFile] = File(...)):
return [{'name': [Link], 'type': f.content_type} for f in files]
13.2 Static Files
from [Link] import StaticFiles
[Link]('/static', StaticFiles(directory='static'), name='static')
# Access: [Link]
13.3 Streaming Response
from [Link] import StreamingResponse
import io
@[Link]('/download/')
def download_file():
content = generate_csv_data()
return StreamingResponse(
[Link](content),
media_type='text/csv',
headers={'Content-Disposition': 'attachment; filename=[Link]'},
)
14. Testing with pytest
pip install httpx pytest pytest-asyncio
14.1 Basic Tests
from [Link] import TestClient
from [Link] import app
client = TestClient(app)
def test_read_root():
resp = [Link]('/')
assert resp.status_code == 200
assert [Link]() == {'message': 'Hello, FastAPI!'}
def test_create_item():
resp = [Link]('/items/', json={'name': 'Widget', 'price': 9.99})
assert resp.status_code == 201
assert [Link]()['name'] == 'Widget'
def test_validation_error():
resp = [Link]('/items/', json={'name': '', 'price': -1})
assert resp.status_code == 422
14.2 Override Dependencies in Tests
def override_get_db():
db = TestingSessionLocal()
try:
yield db
finally:
[Link]()
app.dependency_overrides[get_db] = override_get_db
def test_get_users():
resp = [Link]('/users/')
assert resp.status_code == 200
14.3 Async Tests
import pytest
from httpx import AsyncClient
@[Link]
async def test_async_endpoint():
async with AsyncClient(app=app, base_url='[Link] as ac:
resp = await [Link]('/async-endpoint')
assert resp.status_code == 200
15. Deployment
15.1 Production Server — Gunicorn + Uvicorn Workers
pip install gunicorn uvicorn[standard]
# Run with multiple workers (recommended: 2*CPU + 1)
gunicorn [Link]:app \
--workers 4 \
--worker-class [Link] \
--bind [Link]:8000 \
--timeout 120 \
--access-logfile -
15.2 Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY [Link] .
RUN pip install --no-cache-dir -r [Link]
COPY . .
EXPOSE 8000
CMD ['gunicorn', '[Link]:app',
'--workers', '4',
'--worker-class', '[Link]',
'--bind', '[Link]:8000']
15.3 Environment Configuration
pip install pydantic-settings
# app/[Link]
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
app_name: str = 'My API'
debug: bool = False
database_url: str
secret_key: str
api_key: str
class Config:
env_file = '.env'
settings = Settings()
# .env file (never commit to git!)
DATABASE_URL=postgresql://user:pass@db:5432/mydb
SECRET_KEY=super-secret-key-here
✕ Never commit .env files or secrets to version control.
16. Quick Reference Card
Task Code / Command
Install pip install 'fastapi[standard]'
Run dev server uvicorn [Link]:app --reload
Swagger docs [Link]
Path param @[Link]('/items/{id}') def f(id: int)
Query param def f(skip: int = 0, limit: int = 10)
Request body def f(item: ItemModel)
Status code @[Link]('/', status_code=201)
Raise 404 raise HTTPException(status_code=404, detail='...')
Response model @[Link]('/', response_model=UserOut)
Dependency def f(db = Depends(get_db))
Background task bg.add_task(fn, arg)
CORS app.add_middleware(CORSMiddleware, ...)
Include router app.include_router(router, prefix='/v1')
File upload async def f(file: UploadFile = File(...))
Static files [Link]('/static', StaticFiles(directory='static'))
Override dep in test app.dependency_overrides[dep] = mock_dep
Test client client = TestClient(app)
Prod server gunicorn [Link]:app --worker-class [Link]
Config from env class Settings(BaseSettings): ...
FastAPI Complete Guide — based on FastAPI 0.11x / Pydantic v2. Full docs at [Link]