Project layout
app/
__init__.py
main.py # create_app() factory
config.py # Pydantic Settings
database.py # async engine + session
models.py # SQLAlchemy models
routers/
health.py
items.py
schemas.py # Pydantic I/O schemas
alembic/
env.py
versions/
app/config.py
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings (BaseSettings ):
database_url: str
debug: bool = False
allowed_origins: list [str ] = ["*" ]
model_config = SettingsConfigDict(env_file=".env" , extra="ignore" )
settings = Settings()
app/database.py
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker
from sqlalchemy.orm import DeclarativeBase
engine = create_async_engine(
settings.database_url,
pool_size=10 ,
max_overflow=20 ,
pool_pre_ping=True ,
echo=settings.debug,
)
AsyncSessionLocal = async_sessionmaker(engine, expire_on_commit=False )
class Base (DeclarativeBase ):
pass
async def get_db () -> AsyncSession:
async with AsyncSessionLocal() as session:
yield session
app/main.py
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from .config import settings
from .database import engine, Base
from .routers import health, items
@asynccontextmanager
async def lifespan (app: FastAPI ):
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
yield
await engine.dispose()
def create_app () -> FastAPI:
app = FastAPI(
title="My API" ,
lifespan=lifespan,
docs_url="/api/docs" if settings.debug else None ,
)
app.add_middleware(
CORSMiddleware,
allow_origins=settings.allowed_origins,
allow_methods=["*" ],
allow_headers=["*" ],
)
app.include_router(health.router)
app.include_router(items.router, prefix="/api/v1" )
return app
app = create_app()
app/routers/health.py
from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import text
from ..database import get_db
router = APIRouter(tags=["health" ])
@router.get("/healthz" )
async def health (db: AsyncSession = Depends(get_db ) ):
await db.execute(text("SELECT 1" ))
return {"status" : "ok" }
Dockerfile
FROM python:3.12 -slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn" , "app.main:app" , "--host" , "0.0.0.0" , "--port" , "8000" , "--workers" , "4" ]