FastAPI Best Practices for Production: Complete 2026 Guide
Master FastAPI best practices for production deployment. Security, performance, testing, error handling, monitoring, and scalability patterns for 2026.
FastAPI Best Practices for Production: Complete 2026 Guide
Building a FastAPI application is easy. Building a production-ready FastAPI application that scales, performs well, and stays secure requires following proven best practices.
This comprehensive guide covers everything you need to know to deploy FastAPI applications to production in 2026, based on real-world patterns implemented in the FastLaunchAPI template - a battle-tested starter that eliminates weeks of setup time.
Project Structure Best Practices
Recommended Directory Structure
The FastLaunchAPI template follows this proven production structure:
fastlaunchapi-premium/
├── backend/
│ ├── app/
│ │ ├── routers/ # Feature-based routing
│ │ │ ├── auth/ # Authentication module
│ │ │ │ ├── auth.py # Auth endpoints
│ │ │ │ ├── models.py # User models
│ │ │ │ ├── services.py # Business logic
│ │ │ │ ├── tasks.py # Celery tasks
│ │ │ │ └── oauth_providers.py # Dynamic OAuth
│ │ │ ├── payments/ # Stripe integration
│ │ │ │ ├── payments.py
│ │ │ │ ├── models.py
│ │ │ │ ├── services.py
│ │ │ │ └── tasks.py
│ │ │ └── core/ # System routes
│ │ │ ├── core.py
│ │ │ └── services.py
│ │ │
│ │ ├── db/ # Database layer
│ │ │ └── database.py # Dual session management
│ │ │
│ │ ├── email/ # Email templates
│ │ │ ├── emails.py
│ │ │ └── templates/
│ │ │ ├── verify_user_email.html
│ │ │ └── reset_password_email.html
│ │ │
│ │ └── config/ # Settings
│ │ └── settings.py # Pydantic settings
│ │
│ ├── alembic/ # Database migrations
│ │ └── versions/
│ │
│ ├── celery_setup.py # Celery config + Beat schedule
│ ├── main.py # FastAPI app entry
│ ├── pyproject.toml # uv dependencies
│ └── uv.lock # Locked versions
│
├── docker-compose.yaml # All services
├── Dockerfile # App container
└── .env.example # Environment template
Why this structure works:
- Feature-based modules: Each router contains its models, services, and tasks
- Dual session support: Async sessions for FastAPI, sync sessions for Celery
- Clean separation: Routers handle HTTP, services contain business logic, tasks handle async work
- Scalable: Easy to add new features without restructuring
- Production-proven: Used by 100+ developers in live applications
💡 Pro Tip: This exact structure ships with FastLaunchAPI - no setup needed!
Configuration Management
Use Pydantic Settings with Dynamic Detection
The FastLaunchAPI template uses Pydantic Settings for type-safe, environment-based configuration:
# app/config/settings.py
from pydantic_settings import BaseSettings, SettingsConfigDict
from typing import Optional
class Settings(BaseSettings):
# API Settings
PROJECT_NAME: str = "FastLaunchAPI"
API_V1_STR: str = "/api/v1"
# Security
SECRET_KEY: str # JWT signing key (required)
ALGORITHM: str = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
# Database
DATABASE_URL: str # postgresql+asyncpg://...
# OAuth2 - Dynamic provider detection
GOOGLE_CLIENT_ID: Optional[str] = None
GOOGLE_CLIENT_SECRET: Optional[str] = None
GITHUB_CLIENT_ID: Optional[str] = None
GITHUB_CLIENT_SECRET: Optional[str] = None
# Stripe
STRIPE_SECRET_KEY: str
STRIPE_WEBHOOK_SECRET: str
STRIPE_PRICE_ID: str
# SendGrid Email
SENDGRID_API_KEY: str
FROM_EMAIL: str = "[email protected]"
# Celery + Redis
CELERY_BROKER_URL: str = "redis://localhost:6379/0"
CELERY_RESULT_BACKEND: str = "redis://localhost:6379/0"
# CORS
BACKEND_CORS_ORIGINS: list[str] = ["http://localhost:3000"]
model_config = SettingsConfigDict(
env_file=".env",
case_sensitive=True,
extra="ignore" # Ignore unknown env vars
)
def oauth_providers_configured(self) -> list[str]:
"""Dynamically detect which OAuth providers are configured"""
providers = []
if self.GOOGLE_CLIENT_ID and self.GOOGLE_CLIENT_SECRET:
providers.append("google")
if self.GITHUB_CLIENT_ID and self.GITHUB_CLIENT_SECRET:
providers.append("github")
return providers
settings = Settings()
Best practices:
- ✅ Type safety: Pydantic validates all env vars at startup
- ✅ Auto-conversion: Strings → ints/bools automatically
- ✅ Dynamic detection: OAuth providers auto-configured based on credentials
- ✅ Fail fast: Missing required vars cause startup failure
- ✅ IDE support: Full autocomplete and type hints
.env File Structure
# .env.example (from FastLaunchAPI template)
# Database
DATABASE_URL=postgresql+asyncpg://user:pass@localhost:5432/dbname
# Security
SECRET_KEY=your-super-secret-key-change-in-production
# OAuth2 (optional - add only providers you want)
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-secret
# GITHUB_CLIENT_ID= # Uncomment to enable GitHub login
# Stripe
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_PRICE_ID=price_...
# SendGrid
SENDGRID_API_KEY=SG.xxx
[email protected]
# Celery
CELERY_BROKER_URL=redis://localhost:6379/0
💡 FastLaunchAPI ships with this exact pattern - all environment variables documented and validated!
Database Best Practices
Dual Session Management (Async + Sync)
Critical for production: FastLaunchAPI uses dual session management to handle both async FastAPI endpoints and sync Celery tasks:
# app/db/database.py
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session
from app.config.settings import settings
# ASYNC ENGINE for FastAPI endpoints
async_engine = create_async_engine(
settings.DATABASE_URL,
echo=False,
pool_size=10,
max_overflow=20,
pool_pre_ping=True, # Verify connections before using
)
AsyncSessionLocal = async_sessionmaker(
async_engine,
class_=AsyncSession,
expire_on_commit=False,
autocommit=False,
autoflush=False,
)
# SYNC ENGINE for Celery tasks
# Convert asyncpg URL to psycopg2 for sync operations
sync_db_url = settings.DATABASE_URL.replace(
"postgresql+asyncpg://",
"postgresql+psycopg2://"
)
sync_engine = create_engine(
sync_db_url,
echo=False,
pool_size=5,
max_overflow=10,
pool_pre_ping=True,
)
SyncSessionLocal = sessionmaker(
sync_engine,
class_=Session,
expire_on_commit=False,
autocommit=False,
autoflush=False,
)
# Async dependency for FastAPI routes
async def get_db() -> AsyncSession:
async with AsyncSessionLocal() as session:
try:
yield session
await session.commit()
except Exception:
await session.rollback()
raise
finally:
await session.close()
# Sync context manager for Celery tasks
def get_sync_db() -> Session:
db = SyncSessionLocal()
try:
yield db
db.commit()
except Exception:
db.rollback()
raise
finally:
db.close()
Why dual sessions?
- ✅ Celery tasks are synchronous - can't use async SQLAlchemy
- ✅ FastAPI routes are async - benefit from async performance
- ✅ Same database - psycopg2 and asyncpg work with same PostgreSQL
- ✅ Type safety - IDE knows which session to use where
Usage in Routes
# app/routers/auth/auth.py
from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from app.db.database import get_db
router = APIRouter()
@router.post("/register")
async def register(
email: str,
db: AsyncSession = Depends(get_db) # Async session
):
# Use async SQLAlchemy queries
result = await db.execute(
select(User).where(User.email == email)
)
user = result.scalar_one_or_none()
# ...
Usage in Celery Tasks
# app/routers/auth/tasks.py
from celery_setup import celery_app
from app.db.database import get_sync_db
from app.routers.auth.models import User
from sqlalchemy import select
@celery_app.task
def send_verification_email(user_id: int):
"""Celery task - uses SYNC session"""
with next(get_sync_db()) as db:
# Use sync SQLAlchemy - no await!
user = db.execute(
select(User).where(User.id == user_id)
).scalar_one()
# Send email logic
send_email(user.email, "Verify your account", ...)
# Update user
user.verification_email_sent = True
db.commit()
Critical insights:
- 🔴 Don't use async sessions in Celery - Celery workers are not async
- ✅ Same models work - both sessions use identical SQLAlchemy models
- ✅ FastLaunchAPI handles this - template includes both sessions pre-configured
💡 This dual session pattern ships with FastLaunchAPI - production-tested and ready to use!
Repository Pattern
# app/db/repositories/user_repository.py
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from app.models.user import User
from typing import Optional
class UserRepository:
def __init__(self, session: AsyncSession):
self.session = session
async def get_by_id(self, user_id: int) -> Optional[User]:
result = await self.session.execute(
select(User).where(User.id == user_id)
)
return result.scalar_one_or_none()
async def get_by_email(self, email: str) -> Optional[User]:
result = await self.session.execute(
select(User).where(User.email == email)
)
return result.scalar_one_or_none()
async def create(self, user: User) -> User:
self.session.add(user)
await self.session.flush()
await self.session.refresh(user)
return user
async def update(self, user: User) -> User:
await self.session.merge(user)
await self.session.flush()
return user
Benefits:
- Separation of database logic
- Easy to mock for testing
- Reusable queries
- Type-safe operations
Error Handling Best Practices
Custom Exception Handler
# app/core/exceptions.py
from fastapi import HTTPException, Request
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException as StarletteHTTPException
class AppException(Exception):
"""Base exception for application errors"""
def __init__(self, message: str, status_code: int = 500):
self.message = message
self.status_code = status_code
class NotFoundException(AppException):
def __init__(self, resource: str):
super().__init__(f"{resource} not found", 404)
class UnauthorizedException(AppException):
def __init__(self, message: str = "Unauthorized"):
super().__init__(message, 401)
# Exception handlers
async def app_exception_handler(request: Request, exc: AppException):
return JSONResponse(
status_code=exc.status_code,
content={
"error": exc.message,
"path": request.url.path,
"method": request.method,
}
)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
return JSONResponse(
status_code=422,
content={
"error": "Validation error",
"details": exc.errors(),
}
)
# app/main.py
from app.core.exceptions import *
app = FastAPI()
app.add_exception_handler(AppException, app_exception_handler)
app.add_exception_handler(RequestValidationError, validation_exception_handler)
Structured Logging
# app/core/logging.py
import logging
import json
from datetime import datetime
class JSONFormatter(logging.Formatter):
def format(self, record):
log_data = {
"timestamp": datetime.utcnow().isoformat(),
"level": record.levelname,
"message": record.getMessage(),
"module": record.module,
"function": record.funcName,
}
if record.exc_info:
log_data["exception"] = self.formatException(record.exc_info)
return json.dumps(log_data)
def setup_logging():
handler = logging.StreamHandler()
handler.setFormatter(JSONFormatter())
logger = logging.getLogger("app")
logger.addHandler(handler)
logger.setLevel(logging.INFO)
return logger
logger = setup_logging()
Security Best Practices
JWT Authentication + OAuth2
FastLaunchAPI implements industry-standard JWT + OAuth2 with dynamic provider detection:
# app/routers/auth/services.py
from datetime import datetime, timedelta
from jose import JWTError, jwt
from passlib.context import CryptContext
from app.config.settings import settings
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def verify_password(plain_password: str, hashed_password: str) -> bool:
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password: str) -> str:
return pwd_context.hash(password)
def create_access_token(user_id: int, expires_delta: timedelta | None = None):
"""Create JWT access token"""
to_encode = {"sub": str(user_id), "type": "access"}
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(
minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES
)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(
to_encode,
settings.SECRET_KEY,
algorithm=settings.ALGORITHM
)
return encoded_jwt
def decode_token(token: str) -> dict:
"""Decode and validate JWT token"""
try:
payload = jwt.decode(
token,
settings.SECRET_KEY,
algorithms=[settings.ALGORITHM]
)
return payload
except JWTError:
return None
OAuth2 with Dynamic Provider Detection:
# app/routers/auth/oauth_providers.py
from app.config.settings import settings
def get_oauth_config(provider: str) -> dict:
"""Dynamically configure OAuth providers from env vars"""
providers = {
"google": {
"client_id": settings.GOOGLE_CLIENT_ID,
"client_secret": settings.GOOGLE_CLIENT_SECRET,
"authorize_url": "https://accounts.google.com/o/oauth2/auth",
"token_url": "https://oauth2.googleapis.com/token",
"userinfo_url": "https://www.googleapis.com/oauth2/v1/userinfo",
},
"github": {
"client_id": settings.GITHUB_CLIENT_ID,
"client_secret": settings.GITHUB_CLIENT_SECRET,
"authorize_url": "https://github.com/login/oauth/authorize",
"token_url": "https://github.com/login/oauth/access_token",
"userinfo_url": "https://api.github.com/user",
},
}
# Only return configured providers
if provider in providers:
config = providers[provider]
if config["client_id"] and config["client_secret"]:
return config
return None
@router.get("/oauth/{provider}")
async def oauth_login(provider: str):
"""Initiate OAuth flow - only for configured providers"""
oauth_config = get_oauth_config(provider)
if not oauth_config:
raise HTTPException(400, f"Provider {provider} not configured")
# Generate OAuth URL
return {"authorization_url": oauth_config["authorize_url"]}
Protected Routes:
# app/routers/auth/auth.py
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from app.routers.auth.services import decode_token
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")
async def get_current_user(
token: str = Depends(oauth2_scheme),
db: AsyncSession = Depends(get_db)
):
"""Dependency to get authenticated user"""
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
payload = decode_token(token)
if not payload:
raise credentials_exception
user_id: str = payload.get("sub")
if user_id is None:
raise credentials_exception
# Get user from database
result = await db.execute(
select(User).where(User.id == int(user_id))
)
user = result.scalar_one_or_none()
if user is None:
raise credentials_exception
return user
# Protected endpoint example
@router.get("/me")
async def get_me(current_user: User = Depends(get_current_user)):
"""Get current authenticated user"""
return current_user
Key security features:
- ✅ Bcrypt password hashing with proper salt rounds
- ✅ JWT tokens with expiration validation
- ✅ OAuth2 flow with state parameter for CSRF protection
- ✅ Dynamic providers - enable/disable via environment variables
- ✅ Token refresh pattern supported
💡 FastLaunchAPI includes complete auth - JWT, OAuth2 (Google/GitHub), email verification, password reset!
CORS Configuration
# app/main.py
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=settings.allowed_origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
expose_headers=["X-Total-Count"], # Custom headers
)
Performance Optimization
Caching with Redis
# app/core/cache.py
from redis.asyncio import Redis
from typing import Optional
import json
class CacheService:
def __init__(self, redis_url: str):
self.redis = Redis.from_url(redis_url, decode_responses=True)
async def get(self, key: str) -> Optional[dict]:
data = await self.redis.get(key)
return json.loads(data) if data else None
async def set(self, key: str, value: dict, expire: int = 3600):
await self.redis.set(key, json.dumps(value), ex=expire)
async def delete(self, key: str):
await self.redis.delete(key)
async def clear_pattern(self, pattern: str):
keys = await self.redis.keys(pattern)
if keys:
await self.redis.delete(*keys)
cache = CacheService(settings.redis_url)
# Usage in routes
@app.get("/users/{user_id}")
async def get_user(user_id: int):
# Check cache
cached = await cache.get(f"user:{user_id}")
if cached:
return cached
# Get from database
user = await user_repository.get_by_id(user_id)
# Cache result
await cache.set(f"user:{user_id}", user.dict(), expire=300)
return user
Background Tasks with Celery
FastLaunchAPI uses Celery + Redis for robust background task processing:
# celery_setup.py
from celery import Celery
from celery.schedules import crontab
from app.config.settings import settings
celery_app = Celery(
"fastlaunchapi",
broker=settings.CELERY_BROKER_URL,
backend=settings.CELERY_RESULT_BACKEND,
)
celery_app.conf.update(
task_serializer="json",
accept_content=["json"],
result_serializer="json",
timezone="UTC",
enable_utc=True,
task_track_started=True,
task_time_limit=300, # 5 minutes max
)
# Celery Beat Schedule (periodic tasks)
celery_app.conf.beat_schedule = {
'cleanup-expired-tokens': {
'task': 'app.routers.auth.tasks.cleanup_expired_tokens',
'schedule': crontab(hour=2, minute=0), # 2 AM daily
},
'send-payment-reminders': {
'task': 'app.routers.payments.tasks.send_payment_reminders',
'schedule': crontab(hour=10, minute=0), # 10 AM daily
},
}
Using Celery Tasks:
# app/routers/auth/tasks.py
from celery_setup import celery_app
from app.email.emails import send_email
from app.db.database import get_sync_db # Use sync session!
from app.routers.auth.models import User
from sqlalchemy import select
@celery_app.task(bind=True, max_retries=3)
def send_verification_email(self, user_id: int, verification_token: str):
"""Send email verification - retries on failure"""
try:
with next(get_sync_db()) as db:
user = db.execute(
select(User).where(User.id == user_id)
).scalar_one()
send_email(
to_email=user.email,
subject="Verify your account",
html_content=f"Token: {verification_token}",
)
user.verification_email_sent = True
db.commit()
except Exception as exc:
# Retry with exponential backoff
raise self.retry(exc=exc, countdown=60 * (2 ** self.request.retries))
@celery_app.task
def cleanup_expired_tokens():
"""Periodic task - runs daily at 2 AM"""
with next(get_sync_db()) as db:
deleted = db.execute(
delete(User).where(
User.verification_token_expires < datetime.utcnow(),
User.is_verified == False
)
)
db.commit()
return f"Deleted {deleted.rowcount} expired tokens"
Triggering from FastAPI routes:
# app/routers/auth/auth.py
from app.routers.auth.tasks import send_verification_email
@router.post("/register")
async def register(user: UserCreate, db: AsyncSession = Depends(get_db)):
# Create user in database
new_user = User(email=user.email, ...)
db.add(new_user)
await db.commit()
# Trigger Celery task (non-blocking!)
send_verification_email.delay(new_user.id, new_user.verification_token)
return {"message": "Check your email"}
Running Celery + Beat + Flower:
# docker-compose.yaml (from FastLaunchAPI template)
services:
celery_worker:
build: .
command: celery -A celery_setup worker --loglevel=info
depends_on:
- redis
- db
celery_beat:
build: .
command: celery -A celery_setup beat --loglevel=info
depends_on:
- redis
flower:
build: .
command: celery -A celery_setup flower --port=5555
ports:
- "5555:5555"
depends_on:
- redis
Why Celery over BackgroundTasks?
- ✅ Persistent: Tasks survive server restarts
- ✅ Retries: Automatic retry with exponential backoff
- ✅ Monitoring: Flower UI for real-time task monitoring
- ✅ Scheduling: Celery Beat for cron-like periodic tasks
- ✅ Scalable: Spin up multiple worker instances
💡 FastLaunchAPI includes this complete Celery setup with Redis, Beat, and Flower pre-configured!
Database Query Optimization
# Use select_related equivalent
from sqlalchemy.orm import selectinload
async def get_user_with_posts(user_id: int):
result = await session.execute(
select(User)
.options(selectinload(User.posts))
.where(User.id == user_id)
)
return result.scalar_one_or_none()
Testing Best Practices
Test Setup
# tests/conftest.py
import pytest
from fastapi.testclient import TestClient
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
from app.main import app
from app.db.session import get_db
# Test database
TEST_DATABASE_URL = "postgresql+asyncpg://test:test@localhost/test_db"
@pytest.fixture
async def test_db():
engine = create_async_engine(TEST_DATABASE_URL)
TestSessionLocal = sessionmaker(
engine, class_=AsyncSession, expire_on_commit=False
)
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
async with TestSessionLocal() as session:
yield session
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.drop_all)
@pytest.fixture
def client(test_db):
def override_get_db():
yield test_db
app.dependency_overrides[get_db] = override_get_db
with TestClient(app) as c:
yield c
app.dependency_overrides.clear()
@pytest.fixture
async def test_user(test_db):
user = User(email="[email protected]", hashed_password="...")
test_db.add(user)
await test_db.commit()
return user
API Tests
# tests/test_api/test_users.py
import pytest
def test_get_user(client, test_user):
response = client.get(f"/api/v1/users/{test_user.id}")
assert response.status_code == 200
assert response.json()["email"] == test_user.email
def test_create_user(client):
response = client.post(
"/api/v1/users",
json={"email": "[email protected]", "password": "secure123"}
)
assert response.status_code == 201
assert "id" in response.json()
@pytest.mark.asyncio
async def test_user_service(test_db):
service = UserService(test_db)
user = await service.create_user("[email protected]", "password")
assert user.id is not None
Monitoring & Observability
Prometheus Metrics
# app/core/metrics.py
from prometheus_client import Counter, Histogram, generate_latest
from fastapi import Request
import time
REQUEST_COUNT = Counter(
'http_requests_total',
'Total HTTP requests',
['method', 'endpoint', 'status']
)
REQUEST_DURATION = Histogram(
'http_request_duration_seconds',
'HTTP request duration',
['method', 'endpoint']
)
@app.middleware("http")
async def monitor_requests(request: Request, call_next):
start_time = time.time()
response = await call_next(request)
duration = time.time() - start_time
REQUEST_COUNT.labels(
method=request.method,
endpoint=request.url.path,
status=response.status_code
).inc()
REQUEST_DURATION.labels(
method=request.method,
endpoint=request.url.path
).observe(duration)
return response
@app.get("/metrics")
async def metrics():
return Response(generate_latest(), media_type="text/plain")
Health Checks
@app.get("/health")
async def health_check(db: AsyncSession = Depends(get_db)):
try:
# Check database
await db.execute("SELECT 1")
# Check Redis
await cache.redis.ping()
return {
"status": "healthy",
"database": "up",
"cache": "up",
"timestamp": datetime.utcnow()
}
except Exception as e:
return JSONResponse(
status_code=503,
content={"status": "unhealthy", "error": str(e)}
)
Deployment Best Practices
UV Package Manager (10-100x Faster)
FastLaunchAPI uses UV - the modern Rust-based Python package manager:
# pyproject.toml
[project]
name = "fastlaunchapi"
version = "1.0.0"
requires-python = ">=3.11"
dependencies = [
"fastapi>=0.115.0",
"uvicorn[standard]>=0.32.0",
"sqlalchemy>=2.0.0",
"alembic>=1.13.0",
"asyncpg>=0.29.0",
"psycopg2-binary>=2.9.0", # For Celery sync sessions
"pydantic>=2.10.0",
"pydantic-settings>=2.6.0",
"python-jose[cryptography]>=3.3.0",
"passlib[bcrypt]>=1.7.4",
"python-multipart>=0.0.6",
"celery>=5.4.0",
"redis>=5.0.0",
"sendgrid>=6.11.0",
"stripe>=11.0.0",
]
[project.optional-dependencies]
dev = [
"pytest>=8.0.0",
"pytest-asyncio>=0.23.0",
"httpx>=0.27.0",
"ruff>=0.8.0",
]
Installing with UV:
# Install UV (one-time setup)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Install dependencies (10-100x faster than pip!)
uv pip install -r pyproject.toml
# Add new package
uv add stripe
# Development dependencies
uv add --dev pytest
Docker Configuration with Multi-Stage Build
# Dockerfile (from FastLaunchAPI template)
FROM python:3.11-slim as base
# Install UV
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
UV_COMPILE_BYTECODE=1 \
UV_LINK_MODE=copy
WORKDIR /app
# Install dependencies
COPY pyproject.toml uv.lock ./
RUN uv pip install --system --no-cache .
# Copy application
COPY ./app ./app
COPY ./celery_setup.py ./
# Production stage
FROM base as production
# Create non-root user
RUN useradd -m -u 1000 appuser && \
chown -R appuser:appuser /app
USER appuser
# Run with uvicorn
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Docker Compose - Complete Production Stack
# docker-compose.yaml (from FastLaunchAPI template)
services:
db:
image: postgres:16
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: fastlaunchapi
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
redis:
image: redis:7-alpine
ports:
- "6379:6379"
backend:
build: .
command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
volumes:
- ./app:/app/app
ports:
- "8000:8000"
env_file:
- .env
depends_on:
- db
- redis
celery_worker:
build: .
command: celery -A celery_setup worker --loglevel=info
volumes:
- ./app:/app/app
env_file:
- .env
depends_on:
- db
- redis
celery_beat:
build: .
command: celery -A celery_setup beat --loglevel=info
volumes:
- ./app:/app/app
env_file:
- .env
depends_on:
- redis
flower:
build: .
command: celery -A celery_setup flower --port=5555
ports:
- "5555:5555"
env_file:
- .env
depends_on:
- redis
pgadmin:
image: dpage/pgadmin4
environment:
PGADMIN_DEFAULT_EMAIL: [email protected]
PGADMIN_DEFAULT_PASSWORD: admin
ports:
- "5050:80"
volumes:
postgres_data:
Running the stack:
# Start all services
docker-compose up -d
# Run migrations
docker-compose exec backend alembic upgrade head
# View logs
docker-compose logs -f backend
# Monitor Celery tasks
open http://localhost:5555 # Flower UI
Production deployment:
- ✅ PostgreSQL: Production-ready relational database
- ✅ Redis: Caching + Celery broker
- ✅ Celery Worker: Background task processing
- ✅ Celery Beat: Scheduled tasks (cron-like)
- ✅ Flower: Real-time task monitoring
- ✅ pgAdmin: Database management UI
💡 This exact Docker Compose setup ships with FastLaunchAPI - just
docker-compose up!
API Versioning
# app/api/v1/router.py
from fastapi import APIRouter
api_router = APIRouter()
api_router.include_router(users.router, prefix="/users", tags=["users"])
api_router.include_router(items.router, prefix="/items", tags=["items"])
# app/main.py
app.include_router(api_router, prefix="/api/v1")
Documentation Best Practices
Custom OpenAPI Documentation
app = FastAPI(
title="FastAPI Application",
description="Production-ready FastAPI app",
version="1.0.0",
docs_url="/api/docs",
redoc_url="/api/redoc",
openapi_url="/api/openapi.json",
openapi_tags=[
{"name": "users", "description": "User management"},
{"name": "auth", "description": "Authentication"},
]
)
Request/Response Examples
@app.post(
"/users",
response_model=UserResponse,
status_code=201,
responses={
201: {
"description": "User created successfully",
"content": {
"application/json": {
"example": {"id": 1, "email": "[email protected]"}
}
}
},
400: {"description": "Invalid input"},
}
)
async def create_user(user: UserCreate):
"""
Create a new user with the following information:
- **email**: Valid email address
- **password**: Minimum 8 characters
"""
return await user_service.create(user)
Production Checklist
Before deploying to production, ensure these FastLaunchAPI features are configured:
Authentication & Security
- JWT authentication with secure token signing
- OAuth2 (Google/GitHub) with dynamic provider detection
- Password hashing with bcrypt
- Email verification flow
- Password reset functionality
- CORS properly configured
Database & Infrastructure
- Dual session management (async + sync)
- Alembic migrations configured
- Connection pooling enabled
- PostgreSQL with asyncpg + psycopg2
- Database backup strategy
Background Tasks & Email
- Celery + Redis configured
- Celery Beat for scheduled tasks
- Flower monitoring UI
- SendGrid email integration
- HTML email templates
Payments & Business Logic
- Stripe integration
- Webhook handling
- Subscription management
- Payment task queue
DevOps & Deployment
- Docker Compose with all services
- UV package manager for fast installs
- Health check endpoints
- Environment variable validation
- pgAdmin for database management
- Non-root container user
Code Quality
- Feature-based modular structure
- Services pattern for business logic
- Pydantic models for validation
- Async-first architecture
- Type hints throughout
Get FastLaunchAPI - Skip the Setup
All these production best practices are pre-implemented in FastLaunchAPI. Why spend weeks configuring when you can start building features today?
What You Get
FastLaunchAPI Premium → includes everything from this guide:
Backend (FastAPI)
- ✅ Dual session management - Async + sync SQLAlchemy
- ✅ Authentication system - JWT + OAuth2 (Google/GitHub)
- ✅ Stripe integration - Payments + webhooks + subscriptions
- ✅ Celery setup - Redis + Beat + Flower monitoring
- ✅ Email system - SendGrid with HTML templates
- ✅ Database migrations - Alembic pre-configured
- ✅ Docker Compose - PostgreSQL + Redis + Celery + pgAdmin
- ✅ UV package manager - 10-100x faster than pip
- ✅ Modular architecture - Feature-based router structure
Frontend (Next.js 15)
- ✅ Landing page - Homepage + pricing + blog
- ✅ Documentation - Fumadocs with search
- ✅ Stripe checkout - Integrated payment flow
- ✅ Authentication UI - Login + OAuth buttons
- ✅ SEO optimized - Metadata + sitemaps + structured data
- ✅ Responsive design - Mobile-first approach
Developer Experience
- ✅ Complete documentation - 23+ MDX docs covering every feature
- ✅ Environment templates - .env.example with all variables
- ✅ Type safety - TypeScript + Pydantic throughout
- ✅ Hot reload - Fast development workflow
- ✅ Production tested - Used by 100+ developers
Pricing
- Premium: $149 - Complete backend + frontend + docs
- Premium + Support: $249 - Includes priority support + updates
Why FastLaunchAPI?
Time savings:
- ⏱️ 2-3 weeks of backend setup → 5 minutes
- ⏱️ 1 week of auth implementation → Already done
- ⏱️ 3 days of Stripe integration → Included
- ⏱️ 1 week of Docker setup → docker-compose up
Total: Save 4-6 weeks and launch your SaaS faster.
Get FastLaunchAPI Now → | View Documentation
Last updated: January 2026
This blog post is based on real production patterns from FastLaunchAPI - a battle-tested FastAPI template used by 100+ developers.
Related Articles
FastAPI vs Flask for SaaS: Which Framework Wins in 2026?
Comprehensive comparison of FastAPI and Flask for building SaaS applications. Performance benchmarks, feature analysis, and real-world insights to help you choose the right Python framework.
FastAPI vs Django REST Framework: Complete 2026 Comparison Guide
Detailed comparison of FastAPI vs Django REST Framework for building APIs in 2026. Performance benchmarks, features, use cases, and migration guide included.
FastAPI Template with Stripe: Build a Payment-Ready API in 10 Minutes
Learn how to integrate Stripe payments into your FastAPI application using a production-ready template. Complete guide with authentication, webhooks, and subscription management.