A production-ready, reusable FastAPI backend template with:
- β‘ FastAPI + SQLAlchemy 2.0 async ORM
- π JWT auth with refresh tokens and RBAC (Role-Based Access Control)
- ποΈ PostgreSQL (asyncpg) + Redis (caching + Celery broker)
- βοΈ Celery background tasks with Flower monitoring
- π¦ Object storage abstraction (DigitalOcean Spaces / S3-compatible)
- π Alembic async migrations
- π Prometheus metrics via
/metrics - πͺ΅ Rotating file + colored console logging
- π³ Docker Compose ready (multi-stage Dockerfile)
- π Activity log (append-only audit trail)
- ποΈ Release notes (What's New system)
cp .env.example .env
# Edit .env β at minimum set:
# APP_NAME, POSTGRES_*, SECRET_KEY, JWT_SECRET_KEYpip install uv
uv pip install -r pyproject.tomldocker-compose up -d postgres redisalembic upgrade headpython -m app.user.seedNote: Seeding also runs automatically at startup. It is idempotent.
python -m app.user.create_admin# Development (with hot reload)
uvicorn app.core.main:app --reload
# With Docker (all services)
docker-compose up -d| URL | Description |
|---|---|
| http://localhost:8000/docs | Swagger UI |
| http://localhost:8000/redoc | ReDoc |
| http://localhost:8000/health | Health check |
| http://localhost:8000/metrics | Prometheus metrics |
| http://localhost:5555 | Flower (Celery monitoring) |
# Start all services
docker-compose up -d
# View API logs
docker-compose logs -f api
# Stop all services
docker-compose down
# Stop and remove volumes (WARNING: destroys data)
docker-compose down -v| Service | Port | Description |
|---|---|---|
api |
8000 | FastAPI application |
postgres |
5432 | PostgreSQL database |
redis |
6379 | Redis (cache + Celery broker) |
celery_worker |
β | Background task worker |
flower |
5555 | Celery task monitoring UI |
# Start worker locally (without Docker)
celery -A app.core.background.celery_app:celery_app worker --loglevel=info
# Flower monitoring locally
celery -A app.core.background.celery_app:celery_app flower --port=5555app/
βββ core/ # Shared infrastructure (never project-specific)
β βββ main.py # FastAPI app factory + lifespan
β βββ settings.py # Pydantic Settings (env-based config)
β βββ database.py # Async SQLAlchemy engine + session
β βββ models.py # DeclarativeBase for all models
β βββ crud.py # Generic CRUDBase[Model, Create, Update]
β βββ exceptions.py # Global exception handlers
β βββ middleware.py # CORS, GZip, TrustedHost, timing
β βββ logging.py # Rotating + colored console logging
β βββ metrics.py # Prometheus instrumentator
β βββ utils.py # utc_now() and shared helpers
β βββ alembic_models_import.py # Single place to register all models
β βββ background/ # Celery app + task infrastructure
β βββ cache/ # Redis cache abstraction
β βββ object_storage/ # S3-compatible file storage
β
βββ apis/
β βββ v1.py # Aggregates all module routers
β
βββ user/ # Auth + RBAC module
β βββ models.py # User, Role, Permission, RefreshToken
β βββ seed.py # Idempotent role/permission seeder β edit this
β βββ create_admin.py # Interactive super-admin creation CLI
β βββ auth_management/ # JWT login, refresh, logout
β βββ permission_management/ # RBAC scoped access helpers
β βββ crud/ # CRUD for users, roles, permissions, tokens
β βββ schemas/ # Pydantic schemas
β βββ services/ # Business logic
β βββ routes/ # FastAPI routers
β
βββ activity/ # Append-only audit log module
βββ release_notes/ # "What's New" release notes module
β
βββ <your_module>/ # Add new feature modules here
βββ models/ # SQLAlchemy models
βββ schemas/ # Pydantic schemas
βββ crud/ # CRUD operations
βββ services/ # Business logic
βββ routes/ # FastAPI routers
βββ dependencies.py # FastAPI Depends() helpers
βββ exceptions.py # Module-specific exceptions
βββ enums.py # Module enums
βββ permissions.py # Permission constants
migrations/ # Alembic migration files
docker/ # Dockerfile + entrypoint scripts
Full standards: engineering.hybridinteractive.in/standards/project-structure
This project follows the Hybrid Interactive Engineering Standards β a Modular Monolith architecture based on Domain-Driven Design (DDD). Files are grouped by feature/domain (e.g., user/, product/), not by technical concern (not all models in one folder, all routes in another).
| Principle | What It Means Here |
|---|---|
| Separation of Concerns | HTTP routing, business logic, and database access are strictly decoupled into independent layers |
| Dependency Injection | Use FastAPI Depends() to pass sessions, user context, and config β never import them directly in routes |
| Async First | All I/O operations (DB, HTTP, file) must use async/await |
| Strict Typing | All inputs/outputs use Pydantic schemas with explicit validation β no raw dict returns |
Every domain feature lives in its own vertical slice:
app/<feature_name>/
βββ __init__.py # Module public API β exports only what consumers need
βββ models/ # ORM entity classes defining database tables
βββ schemas/ # Pydantic models for request validation & response shaping
βββ crud/ # Repository layer β exclusively DB interactions
βββ services/ # Business logic β orchestrates crud, cache, external calls
βββ routes/ # FastAPI routers β HTTP interface only
βββ dependencies.py # Feature-specific FastAPI Depends() providers
βββ enums.py # Feature-level enums and constants
βββ exceptions.py # Domain-specific error classes
βββ tasks.py # Celery background tasks (if applicable)
βββ tests/ # Unit and integration tests for this feature
- Purpose: Parse HTTP inputs β call service β return HTTP response
- β NO business logic β no loops, conditionals, or rule validations
- β NO direct DB queries β never import or call
session.execute()directly - β
Rely entirely on
Depends()for services and DB session - β Catch domain exceptions raised by services and map them to HTTP status codes
# β
Correct route
@router.post("/leads", response_model=LeadResponse)
async def create_lead(
data: LeadCreate,
session: AsyncSession = Depends(get_session),
current_user: User = Depends(get_current_active_user),
_: None = Depends(require_permission("leads:create")),
):
return await lead_service.create(session, data, created_by=current_user)- Purpose: Execute business rules, coordinate between crud/cache/external systems
- β
Owns transaction boundaries β only services call
session.commit()orsession.rollback() - β Must be completely agnostic to HTTP β no
RequestorResponseobjects here - β
Interacts with the DB exclusively through the module's
crud/instances
# β
Correct service β owns commit
async def create(self, session: AsyncSession, data: LeadCreate, created_by: User) -> Lead:
lead = await lead_crud.create(session, obj_in=data)
await session.commit() # β service owns this, always
return lead- Purpose: Encapsulate SQL queries behind clean Python interfaces
- β
Inherit from
app/core/crud.py'sCRUDBasewherever possible - β NEVER call
session.commit()β onlysession.add()andsession.flush() - β Methods are purely data-centric β no business conditions or rules
# β
Correct CRUD β flush only, no commit
async def create(self, session: AsyncSession, *, obj_in: LeadCreate) -> Lead:
db_obj = Lead(**obj_in.model_dump())
session.add(db_obj)
await session.flush() # β get DB-generated ID, never commit
await session.refresh(db_obj)
return db_obj- Purpose: Define structured request/response validation
- β
Split by intent:
LeadCreate,LeadUpdate,LeadResponseβ separate classes - β Enforce validation rules (string lengths, regex, value bounds) at schema level
- β Never return raw ORM objects from routes β always use a
Responseschema
Follow this exact sequence (from the engineering standard):
1. schemas/ β Define request, update, and response Pydantic models
2. models/ β Define the SQLAlchemy ORM model and run migration
3. crud/ β Implement data access methods (extend CRUDBase)
4. services/ β Write business logic, call crud, commit transactions
5. routes/ β Expose HTTP endpoints, inject dependencies, use schemas
6. tests/ β Write tests covering the new behavior
- β Never raise a raw
HTTPException(status_code=500)from inside services or CRUD - β
Define domain exceptions in the module's
exceptions.py:
# app/product/exceptions.py
from fastapi import HTTPException, status
class ProductNotFoundException(HTTPException):
def __init__(self, product_id: str):
super().__init__(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Product '{product_id}' not found"
)- β
Global exception handlers in
app/core/exceptions.pytranslate unhandled exceptions to safe HTTP responses - β Routes may catch domain exceptions and re-raise or return appropriate responses
β‘ 1. Create app/<feature>/ with the standard sub-structure
β‘ 2. Define schemas first β app/<feature>/schemas/
β‘ 3. Add ORM model β app/<feature>/models/
β‘ 4. Register model β app/core/alembic_models_import.py
β‘ 5. Run migration β alembic revision --autogenerate -m "<description>"
β‘ 6. Apply migration β alembic upgrade head
β‘ 7. Implement CRUD β app/<feature>/crud/ (extend CRUDBase)
β‘ 8. Implement service β app/<feature>/services/
β‘ 9. Add permissions β app/user/seed.py (PERMISSIONS + ROLE_PERMISSIONS)
β‘ 10. Define routes β app/<feature>/routes/
β‘ 11. Register router β app/apis/v1.py
β‘ 12. Write tests β app/<feature>/tests/
See docs/PROJECT_CONVENTIONS.md for the full in-depth guide.
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/v1/auth/register |
Register user |
POST |
/api/v1/auth/login |
Login (returns access + refresh tokens) |
POST |
/api/v1/auth/refresh |
Refresh access token |
POST |
/api/v1/auth/logout |
Logout (revoke refresh token) |
GET |
/api/v1/auth/me |
Get current user |
PUT |
/api/v1/auth/me |
Update profile |
All configuration is via environment variables (.env file). Key variables:
| Variable | Required | Description |
|---|---|---|
APP_NAME |
No | Application name (default: MyApp) |
POSTGRES_USER |
Yes | Database username |
POSTGRES_PASSWORD |
Yes | Database password |
POSTGRES_DB |
Yes | Database name |
SECRET_KEY |
Yes | Min 32 chars β for signing |
JWT_SECRET_KEY |
Yes | Min 32 chars β for JWT tokens |
REDIS_HOST |
No | Redis host (default: localhost) |
ENVIRONMENT |
No | development/staging/production |
See .env.example for the full list.