DEV Community

郑沛沛
郑沛沛

Posted on

FastAPI Beyond the Basics: Patterns for Production-Ready APIs

FastAPI is great for quick prototypes, but production apps need more structure. Here are patterns that scale.

Project Structure

myapp/
  app/
    __init__.py
    main.py
    config.py
    dependencies.py
    routers/
      __init__.py
      users.py
      orders.py
    models/
      __init__.py
      user.py
      order.py
    schemas/
      __init__.py
      user.py
      order.py
    services/
      __init__.py
      user_service.py
Enter fullscreen mode Exit fullscreen mode

Configuration with Pydantic Settings

# app/config.py
from pydantic_settings import BaseSettings
from functools import lru_cache

class Settings(BaseSettings):
    database_url: str
    redis_url: str = "redis://localhost:6379"
    secret_key: str
    debug: bool = False
    allowed_origins: list[str] = ["http://localhost:3000"]

    class Config:
        env_file = ".env"

@lru_cache
def get_settings() -> Settings:
    return Settings()
Enter fullscreen mode Exit fullscreen mode

Router Organization

# app/routers/users.py
from fastapi import APIRouter, Depends, HTTPException, status
from app.schemas.user import UserCreate, UserResponse
from app.services.user_service import UserService
from app.dependencies import get_user_service

router = APIRouter(prefix="/users", tags=["users"])

@router.post("/", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
async def create_user(user: UserCreate, service: UserService = Depends(get_user_service)):
    existing = await service.get_by_email(user.email)
    if existing:
        raise HTTPException(status_code=409, detail="Email already registered")
    return await service.create(user)

@router.get("/{user_id}", response_model=UserResponse)
async def get_user(user_id: int, service: UserService = Depends(get_user_service)):
    user = await service.get(user_id)
    if not user:
        raise HTTPException(status_code=404, detail="User not found")
    return user
Enter fullscreen mode Exit fullscreen mode

Dependency Injection

# app/dependencies.py
from fastapi import Depends
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db
from app.services.user_service import UserService

async def get_user_service(db: AsyncSession = Depends(get_db)) -> UserService:
    return UserService(db)
Enter fullscreen mode Exit fullscreen mode

Error Handling

# app/main.py
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

app = FastAPI()

class AppException(Exception):
    def __init__(self, status_code: int, detail: str):
        self.status_code = status_code
        self.detail = detail

@app.exception_handler(AppException)
async def app_exception_handler(request: Request, exc: AppException):
    return JSONResponse(status_code=exc.status_code, content={"error": exc.detail})

@app.exception_handler(Exception)
async def generic_exception_handler(request: Request, exc: Exception):
    return JSONResponse(status_code=500, content={"error": "Internal server error"})
Enter fullscreen mode Exit fullscreen mode

Middleware Stack

from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.trustedhost import TrustedHostMiddleware
import time

app.add_middleware(
    CORSMiddleware,
    allow_origins=settings.allowed_origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

@app.middleware("http")
async def add_timing_header(request: Request, call_next):
    start = time.time()
    response = await call_next(request)
    response.headers["X-Process-Time"] = str(time.time() - start)
    return response
Enter fullscreen mode Exit fullscreen mode

Background Tasks

from fastapi import BackgroundTasks

async def send_welcome_email(email: str):
    # slow email sending logic
    pass

@router.post("/register")
async def register(user: UserCreate, bg: BackgroundTasks):
    new_user = await service.create(user)
    bg.add_task(send_welcome_email, user.email)
    return new_user  # returns immediately
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  1. Separate routers, schemas, models, and services
  2. Use Pydantic Settings for configuration
  3. Dependency injection keeps code testable
  4. Custom exception handlers for consistent error responses
  5. Background tasks for non-blocking operations

6. Always add CORS middleware for frontend integration

🚀 Level up your AI workflow! Check out my AI Developer Mega Prompt Pack — 80 battle-tested prompts for developers. $9.99

Top comments (0)