A Proof of Concept (POC) for a social media application built with FastAPI, featuring Posts, Comments, Likes, User Authentication, Registration, and Email Confirmation. Built on an enterprise-grade architecture template with modular components, observability, resilience, and a scalable development workflow.
This POC demonstrates a complete social media backend with the following core features:
- β User Registration - Secure user signup with email and password validation
- β User Authentication - JWT-based authentication system
- β Email Confirmation - Email verification workflow for new users
- β User Profile - User information management (email, full name, status)
- β Posts - Create, read, update, and delete user posts (with optional image URLs)
- β Comments - Add comments to posts with full CRUD operations
- β Likes - Like/unlike posts with automatic like count tracking
- β Post Feed - View posts with associated comments and like counts
- β Clean Architecture - Separation of concerns (API β Service β Data layers)
- β Multi-Database Support - Supabase, PostgreSQL, SQLite providers
- β Type Safety - Full type hints with Pydantic v2 and SQLAlchemy 2.0
- β
Modern Dependency Injection - Using
Annotatedpattern (Pydantic v2 & FastAPI best practices) - β Security - JWT authentication, password hashing, email confirmation
- β Observability - Structured logging, metrics, health checks
ββββββββββββββββββββββββββββββ
β API Gateway β
β (FastAPI Application) β
ββββββββββββββββ¬ββββββββββββββ
β
HTTP Requests (REST API)
β
ββββββββββββββββ΄ββββββββββββββ
β API Router Layer β
β β
β /api/v1/auth β
β ββ POST /token β
β ββ GET /me β
β β
β /api/v1/users β
β ββ POST /register β
β ββ GET /confirm/{token}β
β ββ GET /me β
β ββ GET / β
β ββ GET /{user_id} β
β β
β /api/v1/posts β
β ββ GET / β
β ββ POST / β
β ββ GET /{post_id} β
β β
β /api/v1/comments β
β ββ GET / β
β ββ POST / β
β ββ GET /posts/{post_id}/commentsβ
β ββ PATCH /{comment_id} β
β ββ DELETE /{comment_id} β
β β
β /api/v1/posts/{post_id}/likesβ
β ββ POST / β
β ββ DELETE / β
β ββ GET /count β
β ββ GET /me β
ββββββββββββββββ¬ββββββββββββββ
β
ββββββββββββββββ΄ββββββββββββββ
β Business Logic Layer β
β (Domain/Service Layer) β
β β
β β’ User Registration β
β β’ Authentication (JWT) β
β β’ Email Confirmation β
β β’ Post Management β
β β’ Comment Operations β
β β’ Like Operations β
β β’ Authorization Checks β
ββββββββββββββββ¬ββββββββββββββ
β
βββββββββββββββββ¬ββββββββββββββΌββββββββββββββββ
β β β β
ββββββββββΌβββββββββ ββββββΌββββββ βββββββΌββββββ βββββββΌββββββ
β Data Layer β β Backgroundβ β External β β Security β
β β β Tasks β β Services β β Layer β
β β’ SQLAlchemy 2.0β β β β β β β
β β’ Async ORM β β β’ Email β β β’ Mailgun β β β’ JWT β
β β’ Repositories β β β’ Async β β β’ Supabaseβ β β’ OAuth2 β
β β β β’ Files β β β’ Auth β β β’ Passwordβ
β β’ Users Table β β β β β β Hashingβ
β β’ Posts Table β β β β β β β’ RLS β
β β’ Comments Tableβ β β β β β β
β β’ Likes Table β β β β β β β
β β’ Alembic β β β β β β β
β Migrations β β β β β β β
βββββββββββ¬ββββββββ βββββββ¬ββββββ βββββββ¬ββββββ βββββββ¬ββββββββ
β β β β
βββββββββββββββββΌββββββββββββββΌββββββββββββββ
β β
ββββββββββΌββββββββββββββΌβββββββββ
β Database Providers β
β β
β β’ Supabase (PostgreSQL) β
β β’ PostgreSQL β
β β’ SQLite (Testing) β
β β’ MongoDB (Optional) β
β β’ Qdrant (Vector DB, Optional) β
βββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββΌββββββββββββββββ
β β β
βββββββββββΌβββββββββ ββββββΌββββββ βββββββΌββββββ
β Observability β β Rate β β Config β
β β β Limiting β β Managementβ
β β’ Structured β β β β β
β Logging β β β’ Per β β β’ Env β
β β’ Request β β endpointβ β Configsβ
β Correlation IDsβ β β’ Global β β β’ Feature β
β β’ Metrics β β rules β β Flags β
β β’ Health Checks β β β β β
ββββββββββββββββββββ ββββββββββββ βββββββββββββ
fastapi-template/
βββ app/ # Main application package
β βββ alembic/ # Alembic migrations directory
β β βββ env.py # Alembic environment configuration
β β βββ versions/ # Migration files
β β β βββ 47984c963225_initial_migration.py
β β βββ script.py.mako # Migration template
β β βββ README # Alembic documentation
β βββ alembic.ini # Alembic configuration file
β βββ api/ # API layer
β β βββ v1/ # API version 1
β β βββ __init__.py # Router aggregation
β β βββ routers/ # REST endpoints by domain
β β βββ auth.py # Authentication endpoints (POST /token, GET /me)
β β βββ users.py # User management endpoints (register, confirm, profile)
β β βββ posts.py # Post CRUD endpoints
β β βββ comments.py # Comment endpoints
β β βββ likes.py # Like/unlike endpoints
β βββ core/ # Core functionality & configuration
β β βββ config.py # Environment-aware configuration (Pydantic Settings)
β β βββ logging.py # Structured logging setup (structlog)
β β βββ auth.py # JWT authentication, password hashing
β β βββ deps.py # FastAPI dependency injection
β β βββ rate_limit.py # Rate limiting middleware
β β βββ tasks.py # Background tasks (email sending)
β βββ db/ # Database layer
β β βββ postgresql.py # PostgreSQL provider (Supabase/PostgreSQL)
β β βββ sqlite.py # SQLite provider (testing)
β β βββ mongodb.py # MongoDB provider (optional)
β β βββ qdrant.py # Qdrant vector DB provider (optional)
β β βββ base.py # Base database classes
β β βββ session.py # Database session management
β β βββ repositories/ # Repository pattern implementation
β β βββ base_repository.py # Base repository with common CRUD
β β βββ user_repository.py # User data access
β β βββ post_repository.py # Post data access
β β βββ comment_repository.py # Comment data access
β β βββ like_repository.py # Like data access
β βββ models/ # SQLAlchemy 2.0 ORM models
β β βββ __init__.py # Model exports (Base, User, Post, etc.)
β β βββ user.py # User model (auth, profile, relationships)
β β βββ post.py # Post, Comment, Like models
β βββ schemas/ # Pydantic v2 schemas (request/response)
β β βββ user.py # User schemas (create, update, response)
β β βββ post.py # Post, Comment, Like schemas
β β βββ auth.py # Authentication schemas (token, login)
β β βββ common.py # Common schemas (pagination, errors)
β βββ services/ # Business logic layer
β β βββ user_service.py # User registration, confirmation, profile
β β βββ post_service.py # Post business logic
β β βββ comment_service.py # Comment business logic
β β βββ like_service.py # Like business logic
β βββ tools/ # Utility scripts and tools
β β βββ backend_pre_start.py # Wait for DB and validate connection
β β βββ tests_pre_start.py # Validate test environment setup
β β βββ initial_data.py # Create initial database data (superuser)
β β βββ clear_alembic_version.py # Clear Alembic version table
β βββ utils/ # Helper utilities
β β βββ sanitization.py # String sanitization utilities
β βββ main.py # FastAPI application entry point
β βββ __init__.py
βββ tests/ # Test suite (TDD structure)
β βββ __init__.py
β βββ conftest.py # Shared pytest fixtures (db_session, client, etc.)
β βββ README.md # Test documentation and guidelines
β βββ unit/ # Unit tests (fast, isolated, no DB)
β β βββ __init__.py
β β βββ conftest.py # Unit test fixtures
β β βββ test_user_service.py # Service layer unit tests
β β βββ test_post_service.py
β β βββ test_comment_service.py
β β βββ test_like_service.py
β β βββ test_user_repository.py # Repository unit tests
β β βββ test_security.py # Security unit tests
β βββ integration/ # Integration tests (with DB)
β β βββ __init__.py
β β βββ conftest.py # Integration test fixtures
β β βββ test_dependencies.py # Dependency injection tests
β β βββ test_routes_posts.py # Post route integration tests
β β βββ test_routes_users.py # User route integration tests
β βββ e2e/ # End-to-end tests (full API flow)
β β βββ __init__.py
β β βββ conftest.py # E2E test fixtures
β β βββ test_users_api.py # User API E2E tests
β β βββ test_posts_api.py # Post API E2E tests
β βββ acceptance/ # Acceptance tests (user journeys)
β β βββ __init__.py
β β βββ conftest.py
β β βββ test_user_journey.py # Complete user journey tests
β βββ security/ # Security-focused tests
β β βββ __init__.py
β β βββ test_auth.py # Authentication tests
β β βββ test_authorization.py # Authorization tests
β β βββ test_input_validation.py # Input validation tests
β βββ schema/ # OpenAPI schema validation tests
β β βββ __init__.py
β β βββ test_openapi.py # Schema validation tests
β βββ migrations/ # Migration tests
β βββ __init__.py
β βββ test_alembic_migrations.py # Migration validation tests
βββ configs/ # Environment-specific configuration files
β βββ dev.env.example # Development environment template
β βββ staging.env.example # Staging environment template
β βββ prod.env.example # Production environment template
β βββ test.env.example # Test environment template
βββ scripts/ # Development and utility scripts
β βββ pre_start.sh # Pre-start script (DB wait, migrations, initial data)
β βββ tests_start.sh # Test environment validation and test runner
β βββ lint.sh # Run ruff linter with auto-fix
β βββ format.sh # Format code with ruff
β βββ test.sh # Run pytest tests
β βββ fix.sh # Fix linting and format code (lint + format)
β βββ check.sh # Run all checks without modifying files
βββ docs/ # Comprehensive documentation
β βββ 01-architecture-overview.md
β βββ 02-database-architecture.md
β βββ 00-notes.md # Development notes
β βββ README.md # Documentation index
βββ .dockerignore # Docker ignore patterns
βββ .gitignore # Git ignore patterns
βββ alembic.ini # Root Alembic config (legacy, use app/alembic.ini)
βββ docker-compose.yml # Docker Compose configuration
βββ docker-compose.sh # Docker Compose helper script
βββ Dockerfile # Docker image definition
βββ Makefile # Makefile with common development tasks
βββ main.py # Alternative entry point (legacy)
βββ pyproject.toml # Python project configuration
βββ pytest.ini # Pytest configuration
βββ requirements.txt # Production dependencies
βββ requirements-dev.txt # Development dependencies
βββ uv.lock # UV lock file (dependency versions)
βββ README.md # This file
app/alembic/: Database migrations are stored here. Usemake migrateorpython -m alembic -c app/alembic.inito manage migrations.app/api/v1/routers/: REST API endpoints organized by domain. Each router handles a specific resource.app/core/: Core functionality including configuration, authentication, logging, and dependencies.app/db/repositories/: Data access layer using the Repository pattern. Abstracts database operations.app/services/: Business logic layer. Contains domain logic and orchestrates between repositories and API.app/models/: SQLAlchemy ORM models. Define database schema and relationships.app/schemas/: Pydantic models for request/response validation and serialization.app/tools/: Utility scripts for database initialization, testing setup, and maintenance.tests/: Comprehensive test suite organized by test type (unit, integration, e2e, acceptance).configs/: Environment-specific configuration templates. Copy.examplefiles and customize.scripts/: Shell scripts for common development tasks. Can be used directly or via Makefile.
- User Model - Authentication, profile, email confirmation status
- Post Model - User-generated content with optional image URLs
- Comment Model - Comments on posts with user attribution
- Like Model - User likes on posts with automatic count tracking
- Full relationships: User β Posts, User β Comments, User β Likes, Post β Comments, Post β Likes
- Clear layer separation (API β Service β Data)
- Domain-driven design principles
- Repository pattern for data access
- Dependency injection throughout
- Multi-provider support: PostgreSQL, SQLite, MongoDB, Qdrant
- Database Provider Selection via
DB_PROVIDERconfiguration (supabase, postgresql, sqlite) - Automatic database URL selection based on provider (Supabase uses
SUPABASE_DB_URL, PostgreSQL usesDATABASE_URL) - SQLAlchemy 2.0 with async support and type safety
- ORM models (
models/) and Pydantic v2 schemas (schemas/) - Migration management with Alembic
- JWT authentication & OAuth2 flows
- User registration with password validation (min 8 chars, digit, uppercase)
- Email confirmation workflow
- Password hashing with bcrypt
- Supabase Auth integration
- Row Level Security (RLS) policies
- Rate limiting (per-endpoint and global)
- CORS configuration and input validation
- FastAPI BackgroundTasks for non-blocking operations
- Email confirmation sending via Mailgun API
- Background email tasks in
app/core/tasks.py - Graceful error handling in dev/test modes
- HTML and plain text email templates
- Structured logging with
structlog - Request correlation IDs
- Prometheus metrics
- Health check endpoints
- Multi-environment configs (
dev,staging,prod,test) - Auto-detection via
get_environment()inconfig.py - Pydantic Settings for type-safe configuration
- Feature flags support
# Application Environment
APP_ENV=dev
# Database Provider (options: supabase, postgresql, sqlite)
DB_PROVIDER=supabase
# Security - Generate with: python -c "import secrets; print(secrets.token_urlsafe(32))"
SECRET_KEY=your-secret-key-here-min-32-chars
JWT_SECRET_KEY=your-jwt-secret-key-here-min-32-chars
JWT_ALGORITHM=HS256
JWT_ACCESS_TOKEN_EXPIRE_MINUTES=1440
JWT_CONFIRMATION_TOKEN_EXPIRE_MINUTES=20
# Database Configuration
# For Supabase (when DB_PROVIDER=supabase):
SUPABASE_URL=https://your-project-id.supabase.co
SUPABASE_KEY=your-supabase-anon-key
SUPABASE_DB_URL=postgresql://postgres:password@db.your-project-id.supabase.co:5432/postgres
SUPABASE_DB_PASSWORD=your-db-password
# For PostgreSQL (when DB_PROVIDER=postgresql):
# DATABASE_URL=postgresql+asyncpg://dev_user:dev_pass@localhost:5432/dev_db
# For SQLite (when DB_PROVIDER=sqlite):
# SQLITE_URL=sqlite+aiosqlite:///./test.db
# Email Service (Mailgun)
MAILGUN_API_KEY=your-mailgun-api-key
MAILGUN_DOMAIN=your-domain.mailgun.org
# CORS
CORS_ORIGINS=http://localhost:3000,http://localhost:8080
# Logging
LOG_LEVEL=INFO
LOG_FORMAT=json
# Optional: Sentry for error tracking
# SENTRY_DSN=your-sentry-dsnThe application supports multiple database providers that can be configured via the DB_PROVIDER environment variable. This allows you to easily switch between Supabase, PostgreSQL, and SQLite without code changes.
supabase- Supabase managed PostgreSQL databasepostgresql- Standard PostgreSQL databasesqlite- SQLite database (useful for testing and development)
DB_PROVIDER=supabase
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_KEY=your-supabase-anon-key
SUPABASE_DB_URL=postgresql://postgres:password@db.your-project.supabase.co:5432/postgres
SUPABASE_DB_PASSWORD=your-db-passwordNote: When DB_PROVIDER=supabase, the application automatically uses SUPABASE_DB_URL for database connections. DATABASE_URL is optional in this case.
DB_PROVIDER=postgresql
DATABASE_URL=postgresql+asyncpg://user:password@localhost:5432/dbname
DATABASE_POOL_SIZE=20
DATABASE_MAX_OVERFLOW=10DB_PROVIDER=sqlite
SQLITE_URL=sqlite+aiosqlite:///./test.dbNote: SQLite is typically used for testing. The application will automatically use SQLite when DB_PROVIDER=sqlite.
-
Configuration Loading: The
DB_PROVIDERsetting is loaded from your environment config file (configs/{environment}.env) -
Database Selection: Based on
DB_PROVIDER, the application:- Initializes the appropriate database provider during startup
- Routes database sessions through the selected provider
- Uses the correct database URL (
SUPABASE_DB_URLfor Supabase,DATABASE_URLfor PostgreSQL)
-
Migrations: Alembic migrations automatically detect and use the correct database URL based on your
DB_PROVIDERsetting. The Alembic configuration is located atapp/alembic.iniand migrations are inapp/alembic/directory. Always use-c app/alembic.iniwhen running Alembic commands directly, or use the Makefile commands (e.g.,make migrate) which handle this automatically.
The application selects the database provider in the following order:
- If
DB_PROVIDER=supabase: UsesSUPABASE_DB_URL(falls back toDATABASE_URLifSUPABASE_DB_URLis not set) - If
DB_PROVIDER=postgresql: UsesDATABASE_URL - If
DB_PROVIDER=sqlite: UsesSQLITE_URL
- Flexibility: Switch between database providers without code changes
- Environment-Specific: Different providers for dev, staging, and production
- Testing: Easy to use SQLite for tests while using PostgreSQL/Supabase in other environments
- Supabase Integration: Seamless integration with Supabase's managed PostgreSQL service
import os
from enum import Enum
from pydantic_settings import BaseSettings
class Environment(str, Enum):
DEV = "dev"
STAGING = "staging"
PRODUCTION = "production"
TEST = "test"
def get_environment() -> Environment:
match os.getenv("APP_ENV", "dev").lower():
case "production" | "prod":
return Environment.PRODUCTION
case "staging" | "stage":
return Environment.STAGING
case "test":
return Environment.TEST
case _:
return Environment.DEV
class Settings(BaseSettings):
project_name: str = "FastAPI Template"
database_url: str
mailgun_api_key: str
secret_key: str
environment: Environment = get_environment()
class Config:
env_file = f"configs/{environment.value}.env"
settings = Settings()from contextlib import asynccontextmanager
from typing import AsyncGenerator
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from asgi_correlation_id import CorrelationIdMiddleware
from app.api.v1 import api_v1_router
from app.core.config import settings
from app.core.logging import setup_logging, get_logger
from app.db.postgresql import _postgresql_provider
from app.db.sqlite import _sqlite_provider
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
"""Lifespan context manager for database connections."""
# Startup
setup_logging()
logger = get_logger(__name__)
if settings.DB_PROVIDER.value == "sqlite":
await _sqlite_provider.connect()
elif settings.DB_PROVIDER.value in ("supabase", "postgresql"):
await _postgresql_provider.connect()
logger.info("Application startup complete")
yield
# Shutdown
if settings.DB_PROVIDER.value == "sqlite":
await _sqlite_provider.disconnect()
elif settings.DB_PROVIDER.value in ("supabase", "postgresql"):
await _postgresql_provider.disconnect()
logger.info("Application shutdown complete")
app = FastAPI(
title=settings.PROJECT_NAME,
version=settings.VERSION,
lifespan=lifespan,
docs_url="/docs" if not settings.is_production else None,
)
# Add middleware
app.add_middleware(CorrelationIdMiddleware)
app.add_middleware(
CORSMiddleware,
allow_origins=settings.CORS_ORIGINS,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Include API routers
app.include_router(api_v1_router)
@app.get("/health")
async def health_check():
return {
"status": "ok",
"environment": settings.ENVIRONMENT.value,
"version": settings.VERSION,
}Note: Router prefixes are defined in each router file (e.g., router = APIRouter(prefix="/auth")), and routers are aggregated in app/api/v1/__init__.py. The application uses a lifespan context manager for proper database connection management.
POST /api/v1/auth/token- User login (OAuth2 password flow)- Form data:
username(email),password - Returns: JWT access token (
access_token,token_type: "bearer") - Requires: Email confirmation before login
- Form data:
GET /api/v1/auth/me- Get current authenticated user info with token details- Returns: User information with token expiration and issued_at timestamps
- Requires: JWT authentication token
POST /api/v1/users/register- User registration- Body: User information (email, password, full_name, etc.)
- Returns: User registration response with confirmation URL
- Background: Sends confirmation email asynchronously
GET /api/v1/users/confirm/{token}- Email confirmation- Path parameter:
token(JWT confirmation token) - Returns: Confirmation status
- Updates: Sets user
confirmedstatus toTrue
- Path parameter:
GET /api/v1/users/me- Get current user profile- Returns: Current authenticated user profile
- Requires: JWT authentication token
PATCH /api/v1/users/me- Update current user profile- Requires: JWT authentication token
- Body: User update data (email, full_name, etc.)
- Returns: Updated user profile
GET /api/v1/users- List all users (paginated)- Query parameters:
skip(default: 0),limit(default: 100, max: 1000) - Returns: List of all users
- Requires: JWT authentication token
- Query parameters:
GET /api/v1/users/{user_id}- Get user by ID- Returns: User information
PATCH /api/v1/users/{user_id}- Update user (partial update)- Requires: JWT authentication token
- Authorization: Users can only update their own profile
- Returns: Updated user information
DELETE /api/v1/users/{user_id}- Delete user- Requires: JWT authentication token
- Authorization: Users can only delete their own account
GET /api/v1/posts- List all posts with sorting options- Query parameters:
sorting(optional): Sort order -new(default),old, ormost_likesskip(optional): Number of records to skip (default: 0)limit(optional): Maximum number of records (default: 100, max: 1000)
- Returns: List of posts with like counts
- Query parameters:
GET /api/v1/posts/{post_id}- Get post details with comments and likes count- Returns: Post with associated comments and like count
POST /api/v1/posts- Create a new post (authenticated)- Requires: JWT authentication token
- Body: Post content (body, optional image_url)
- Returns: Created post
PATCH /api/v1/posts/{post_id}- Update post (authenticated)- Requires: JWT authentication token
- Authorization: Users can only update their own posts
- Body: Updated post content
- Returns: Updated post
DELETE /api/v1/posts/{post_id}- Delete post (authenticated)- Requires: JWT authentication token
- Authorization: Users can only delete their own posts
GET /api/v1/comments- List all comments (paginated)- Query parameters:
skip(default: 0),limit(default: 100, max: 1000) - Returns: List of all comments
- Query parameters:
GET /api/v1/comments/posts/{post_id}/comments- Get comments for a specific post- Query parameters:
skip(default: 0),limit(default: 100, max: 1000) - Returns: List of comments associated with the post
- Query parameters:
POST /api/v1/comments- Create a comment on a post (authenticated)- Requires: JWT authentication token
- Body: Comment content (post_id, body)
- Returns: Created comment
PATCH /api/v1/comments/{comment_id}- Update comment (authenticated)- Requires: JWT authentication token
- Authorization: Users can only update their own comments
- Body: Updated comment content
- Returns: Updated comment
DELETE /api/v1/comments/{comment_id}- Delete comment (authenticated)- Requires: JWT authentication token
- Authorization: Users can only delete their own comments
POST /api/v1/posts/{post_id}/likes- Like a post (authenticated)- Requires: JWT authentication token
- Path parameter:
post_id(the ID of the post to like) - Returns: Like record created
- Note: If already liked, returns existing like
DELETE /api/v1/posts/{post_id}/likes- Unlike a post (authenticated)- Requires: JWT authentication token
- Path parameter:
post_id(the ID of the post to unlike) - Returns: 204 No Content on success
GET /api/v1/posts/{post_id}/likes/count- Get like count for a post- Path parameter:
post_id(the ID of the post) - Returns: Like count for the post
- Path parameter:
GET /api/v1/posts/{post_id}/likes/me- Check if current user liked a post (authenticated)- Requires: JWT authentication token
- Path parameter:
post_id(the ID of the post) - Returns: Like status for the current user
Dockerfile
The Dockerfile is configured for production deployment with proper layer caching and security best practices. See Dockerfile in the project root for the complete implementation.
docker-compose.yml
- Includes PostgreSQL, Qdrant, MongoDB, Redis for local dev environment
Deployment
- Render.com for app hosting
- Supabase for managed PostgreSQL + pgvector
- CI/CD via GitHub Actions
- Docker containerization for consistent environments
Location: app/services/
Purpose: Encapsulate core application logic that implements your business rules and domain operations. This layer sits between the API endpoints and the database, acting as a bridge.
Responsibilities:
-
Enforce business rules
- Example: when a user registers, check for duplicates, hash the password, and send a confirmation email.
-
Coordinate multiple operations
- Example: creating an order might involve updating inventory, logging the action, and notifying a warehouse service.
-
Abstract away infrastructure details
- The API router doesnβt need to know how the database works; the service interacts with the Data Layer.
-
Reusability
- Multiple endpoints or tasks can reuse the same business logic.
- Example: a background task and an API endpoint can both call
user_service.create_user().
-
Unit-testable
- Services can be tested without involving HTTP requests or the database, by mocking the Data Layer.
Example:
# app/services/user_service.py
from app.db.postgresql import get_user_by_email, create_user_in_db
from app.utils.email import send_confirmation_email
class UserService:
def register_user(self, email: str, password: str, full_name: str | None = None):
"""Register a new user and send confirmation email."""
if get_user_by_email(email):
raise ValueError("User already exists")
user = create_user_in_db(email, password, full_name)
confirmation_token = generate_confirmation_token(user.id)
send_confirmation_email(email, token=confirmation_token)
return user
# app/services/post_service.py
class PostService:
def create_post(self, user_id: int, body: str, image_url: str | None = None):
"""Create a new post."""
return create_post_in_db(user_id, body, image_url)
def get_post_with_details(self, post_id: int):
"""Get post with comments and like count."""
post = get_post_by_id(post_id)
comments = get_comments_by_post_id(post_id)
like_count = get_like_count_by_post_id(post_id)
return {
"post": post,
"comments": comments,
"likes": like_count
}Location: app/db/ (and partially app/models/)
Purpose: Handle persistence and retrieval of data, abstracting the details of storage systems. It manages all database interactions for the application.
Responsibilities:
-
Database abstraction
- Encapsulate the details of SQL, NoSQL, or vector databases.
- Example: whether you use PostgreSQL, SQLite, MongoDB, or Qdrant, the Service layer doesnβt need to know.
-
ORM models and sessions
- Define database schema via ORM (SQLAlchemy, MongoEngine, etc.) in
models/. - Manage sessions and transactions for consistent database access.
- Define database schema via ORM (SQLAlchemy, MongoEngine, etc.) in
-
Migrations
- Track schema changes using Alembic for SQL databases.
-
Support multiple DB providers
- Your structure supports swapping or combining multiple databases without changing business logic.
Example:
# app/db/postgresql.py
from sqlalchemy.orm import Session
from app.models.user import User
from app.models.post import Post, Comment, Like
def get_user_by_email(session: Session, email: str):
return session.query(User).filter(User.email == email).first()
def create_user_in_db(session: Session, email: str, password: str, full_name: str | None = None):
user = User(email=email, hashed_password=password, full_name=full_name)
session.add(user)
session.commit()
return user
def create_post_in_db(session: Session, user_id: int, body: str, image_url: str | None = None):
post = Post(user_id=user_id, body=body, image_url=image_url)
session.add(post)
session.commit()
return post
def create_comment_in_db(session: Session, post_id: int, user_id: int, body: str):
comment = Comment(post_id=post_id, user_id=user_id, body=body)
session.add(comment)
session.commit()
return comment
def create_like_in_db(session: Session, post_id: int, user_id: int):
like = Like(post_id=post_id, user_id=user_id)
session.add(like)
session.commit()
return like| Layer | Purpose | Focus | Examples |
|---|---|---|---|
| Business/Domain Logic (Services) | Implement business rules and domain operations | What the app does | Register user, create post, add comment, like post, send confirmation email |
| Data Layer | Handle persistence and data access | How data is stored/retrieved | SQLAlchemy queries, MongoDB operations, migrations |
Rule of thumb:
The Service layer defines what happens in your application. The Data layer defines how data is stored and retrieved.
Comprehensive documentation is available in the docs/ folder:
- Complete Guide: Architecture, Design System, Implementation, and Deployment - Comprehensive step-by-step guide covering everything from architecture to deployment
- Architecture Overview - System architecture, layers, and design principles
- Database Architecture - Multi-database strategy and SQLAlchemy 2.0 patterns
- API Design - REST conventions, versioning, and Pydantic validation
- Service Layer Patterns - Business logic and DDD concepts
- Security Design - Authentication, authorization, and rate limiting
- Configuration Management - Environment handling and settings
- Observability - Logging, metrics, and monitoring
- Async Tasks - Background processing patterns
- Integration Patterns - External service integrations
- Deployment - Docker, CI/CD, and production deployment
- Development Workflow - Setup, testing, and tooling
- Design Patterns & Best Practices - Clean code and SOLID principles
- Python 3.12+
- Docker & Docker Compose (for local development)
- Git
- Virtual environment (venv or similar)
# 1. Clone repository
git clone https://github.com/your-org/fastapi-template.git
cd fastapi-template
# 2. Create virtual environment
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# 3. Install dependencies
pip install -r requirements.txt
pip install -r requirements-dev.txt # For development tools
# 4. Copy environment config
cp configs/dev.env.example configs/dev.env
# Edit configs/dev.env and set your SECRET_KEY, database URLs, etc.
# 5. Start services with Docker Compose
docker-compose up -d
# 6. Initialize database, format code, lint, and test (RECOMMENDED)
make all ENV=dev
# OR manually:
# 6a. Wait for database and run migrations
# python app/tools/backend_pre_start.py
# python -m alembic -c app/alembic.ini upgrade head
# python app/tools/initial_data.py
# 7. Start development server
make dev
# OR: uvicorn app.main:app --reload
# 8. Open API docs
# http://localhost:8000/api/docsFor detailed setup instructions, see Development Workflow.
The fastest way to get started is using the make all command, which automates the entire setup process:
# 1. Install dependencies
make install
# 2. Copy and configure environment
cp configs/dev.env.example configs/dev.env
# Edit configs/dev.env with your settings
# 3. Start Docker services (database, etc.)
docker-compose up -d
# 4. Run everything in one command!
make all ENV=devWhat make all does:
- β
Waits for database to be ready (
backend_pre_start.py) - β
Runs database migrations (
alembic upgrade head) - β
Creates initial data (superuser) (
initial_data.py) - β
Formats code (
ruff format) - β
Lints code (
ruff check) - β
Runs all tests (
pytest)
This ensures your environment is fully set up and validated before you start development!
This project follows Clean Architecture principles with clear separation of concerns:
- API Layer (
app/api/): HTTP endpoints and request/response handling - Service Layer (
app/services/): Business logic and domain rules - Repository Layer (
app/db/repositories/): Data access abstraction - Models (
app/models/): Database schema (SQLAlchemy ORM) - Schemas (
app/schemas/): Request/response validation (Pydantic)
Starting Development:
make dev # Start development server with auto-reloadBefore Committing Code:
make format # Format code
make lint # Check for linting issues
make test # Run all tests
make check # Run lint + type-checkDatabase Operations:
make init-db ENV=dev # Initialize database (first time setup)
make migrate ENV=dev # Run pending migrations
make migrate-revision MESSAGE='description' ENV=dev # Create new migration
make migrate-current ENV=dev # Check current migration versionTesting:
make test # Run all tests
make test-unit # Fast unit tests only
make test-integration # Integration tests with DB
make test-e2e # End-to-end API tests
make test-coverage # Generate coverage reportTroubleshooting:
# Check database connection
python app/tools/backend_pre_start.py
# Clear Alembic version table (if migrations are stuck)
python app/tools/clear_alembic_version.py
# Validate test environment
python app/tools/tests_pre_start.pyThe project uses environment-specific configuration files in configs/:
- Development:
configs/dev.env(local development) - Staging:
configs/staging.env(staging environment) - Production:
configs/prod.env(production environment) - Testing:
configs/test.env(automated tests)
Important Settings:
APP_ENV: Environment name (dev, staging, prod, test)DB_PROVIDER: Database provider (supabase, postgresql, sqlite)DATABASE_URLorSUPABASE_DB_URL: Database connection stringSECRET_KEY: Application secret key (min 32 characters)JWT_SECRET_KEY: JWT token signing key (min 32 characters)
The project supports multiple database providers configured via DB_PROVIDER:
- Supabase: Managed PostgreSQL with auth (
DB_PROVIDER=supabase) - PostgreSQL: Standard PostgreSQL (
DB_PROVIDER=postgresql) - SQLite: File-based database for testing (
DB_PROVIDER=sqlite)
The application automatically selects the correct provider based on your configuration.
Migrations are located in app/alembic/ directory. Always use the Makefile commands or specify -c app/alembic.ini:
# β
Correct way (via Makefile)
make migrate ENV=dev
# β
Correct way (direct command)
python -m alembic -c app/alembic.ini upgrade head
# β Wrong way (will fail)
alembic upgrade head # Missing config path!This project enforces high code quality standards:
- Type Hints: All functions must have type annotations
- Linting: Ruff enforces style and detects errors
- Formatting: Ruff formatter ensures consistent style
- Type Checking: Mypy validates type correctness
- Testing: Comprehensive test coverage required
Run make check before committing to ensure code quality.
This project follows Test-Driven Development (TDD) principles with a comprehensive test suite organized into three categories: unit tests, end-to-end tests, and acceptance tests.
Current Implementation (TDD-focused):
tests/
βββ __init__.py
βββ conftest.py # Shared pytest fixtures and configuration
βββ README.md # Test documentation
βββ unit/ # Unit tests (fast, isolated)
β βββ __init__.py
β βββ conftest.py # Unit test fixtures
β βββ test_user_service.py
β βββ test_post_service.py
β βββ test_comment_service.py
β βββ test_like_service.py
β βββ test_user_repository.py
β βββ test_security.py
βββ integration/ # Integration tests
β βββ __init__.py
β βββ conftest.py # Integration test fixtures
β βββ test_dependencies.py
β βββ test_routes_posts.py
β βββ test_routes_users.py
βββ e2e/ # End-to-end tests (API integration)
β βββ __init__.py
β βββ conftest.py # E2E test fixtures
β βββ test_users_api.py
β βββ test_posts_api.py
βββ acceptance/ # Acceptance tests (user journeys)
β βββ __init__.py
β βββ conftest.py
β βββ test_user_journey.py
βββ security/ # Security tests
β βββ __init__.py
β βββ test_auth.py
β βββ test_authorization.py
β βββ test_input_validation.py
βββ schema/ # OpenAPI schema tests
β βββ __init__.py
β βββ test_openapi.py
βββ migrations/ # Migration tests
βββ __init__.py
βββ test_alembic_migrations.py
Note: See Complete Test Types Reference below for extended structure options including integration, security, performance, and migration tests.
Purpose: Test individual components in isolation with mocked dependencies.
Characteristics:
- β‘ Fast execution (< 1 second per test)
- π No external dependencies (database, network)
- π Use mocks for repositories and external services
- β Test business logic, validation, and error handling
Examples:
- Service layer logic (UserService, PostService, CommentService, LikeService)
- Repository data access patterns
- Security utilities (password hashing, JWT tokens)
- Core utilities
Running Unit Tests:
pytest tests/unit/ -v
pytest tests/unit/ -m unit -vPurpose: Test API endpoints with real database connections.
Characteristics:
- ποΈ Use in-memory SQLite database for fast tests
- π Test full request/response cycle
- β Verify HTTP status codes, response schemas, and error handling
- π Test authentication and authorization
Examples:
- User registration and authentication flows
- CRUD operations on posts, comments, likes
- Authorization and permission checks
- Input validation at API level
Running E2E Tests:
pytest tests/e2e/ -v
pytest tests/e2e/ -m e2e -vPurpose: Test complete user journeys and business scenarios.
Characteristics:
- π€ Test complete workflows from user perspective
- π Multiple API calls in sequence
- β Verify end-to-end business logic
- π Document expected user behavior
Examples:
- User registration β email confirmation β login β create post
- Post creation β receiving likes β receiving comments
- Profile management workflows
Running Acceptance Tests:
pytest tests/acceptance/ -v
pytest tests/acceptance/ -m acceptance -vBelow is a comprehensive list of test types typically used in production FastAPI projects. While not all are required, this serves as a complete reference for enterprise-grade testing strategies.
Small, isolated tests that validate individual functions or classes.
What to test:
- Pure business logic
- Utility functions
- Pydantic models (validation)
- Service-layer functions
- CRUD repository functions (mocked DB)
Tools:
pytestpytest-mockorunittest.mock
Location: tests/unit/
Test multiple components working together, often with actual FastAPI app but mocked external services.
What to test:
- FastAPI routes (using
TestClientorhttpx.AsyncClient) - Database integration (with real or in-memory DB)
- Dependency overrides
- Background tasks
- Auth + middleware interactions
Tools:
pytesthttpx.AsyncClient(recommended for async routes)- Test database (PostgreSQL / SQLite in-memory / Dockerized)
Location: tests/integration/ (can be added)
Full system tests that run the entire stack as realistically as possible.
Characteristics:
- Spin up the FastAPI service (e.g., via Docker Compose)
- Hit real HTTP endpoints
- Use the real database, queues, etc.
Tools:
pytestdocker-composeortestcontainershttpxorrequests
Location: tests/e2e/
Ensure API request/response formats remain compatible across services.
What to test:
- JSON schema validation
- API compatibility between microservices
Tools:
schemathesispydanticmodel validation
Location: tests/contract/ (can be added)
Verify that the automatically generated FastAPI schema is correct.
What to test:
/openapi.jsoncorrectness- Required fields present
- Enum + format values correct
Tools:
pytest- FastAPI's OpenAPI schema validation
Location: tests/schema/ (can be added)
Example:
def test_openapi_schema(client):
"""Test that OpenAPI schema is valid."""
response = client.get("/openapi.json")
assert response.status_code == 200
schema = response.json()
assert "paths" in schema
assert "/api/v1/users/register" in schema["paths"]Check authentication, authorization, and access restrictions.
What to test:
- JWT signature & expiry handling
- 401 / 403 responses for protected routes
- Role-based access control (RBAC)
- Password validation rules
- SQL injection prevention
- XSS prevention
Tools:
pytestpytest-security(optional)
Location: tests/security/ (can be added)
Example:
def test_protected_route_requires_auth(client):
"""Test that protected routes require authentication."""
response = client.get("/api/v1/users/me")
assert response.status_code == 403
def test_invalid_jwt_token(client):
"""Test that invalid JWT tokens are rejected."""
headers = {"Authorization": "Bearer invalid_token"}
response = client.get("/api/v1/users/me", headers=headers)
assert response.status_code == 403Benchmark endpoints under load.
What to test:
- Response time under normal load
- Throughput (requests per second)
- Memory usage
- Database query performance
Tools:
locust- Load testing frameworkk6- Modern load testing toolpytest-benchmark- Micro-benchmarks
Location: tests/performance/ (can be added)
Example:
import pytest
from locust import HttpUser, task
class APIUser(HttpUser):
@task
def get_posts(self):
self.client.get("/api/v1/posts")Ensure DB migrations work end-to-end.
What to test:
- Alembic migrations apply cleanly
- Downgrade/upgrade consistency
- Migration rollback works
- Data integrity after migrations
Tools:
pytestalembic
Location: tests/migrations/ (can be added)
Example:
def test_migrations_upgrade_downgrade(alembic_runner):
"""Test that migrations can be upgraded and downgraded."""
alembic_runner.upgrade("head")
alembic_runner.downgrade("base")
alembic_runner.upgrade("head")If using Celery/RQ/FastAPI BackgroundTasks.
What to test:
- Tasks executed with expected parameters
- Redis queues interactions
- Scheduling (if any)
- Task retry logic
- Task failure handling
Tools:
pytestpytest-celery(if using Celery)- Mock Redis for testing
Location: tests/tasks/ (can be added)
Example:
def test_send_email_task(mock_background_tasks):
"""Test that email sending task is queued."""
# Test that task is added to background tasks
assert len(mock_background_tasks.tasks) == 1Ensure that API responses don't change unexpectedly.
What to test:
- Response structure consistency
- Field names and types
- Error message formats
Tools:
pytest-snapshot- JSON snapshot files
Location: tests/snapshots/ (can be added)
Example:
def test_user_response_snapshot(client, snapshot):
"""Test that user response matches snapshot."""
response = client.get("/api/v1/users/1")
assert response.json() == snapshot- β Unit tests - Fast, isolated component testing
- β Integration tests - Component interaction testing
- β E2E tests - Full system testing with Docker/testcontainers
- β Security tests - Authentication, authorization, input validation
- β Database migration tests - Ensure migrations work correctly
- β OpenAPI contract tests - Verify API schema correctness
- β Performance tests - Load testing for critical endpoints
- β Contract tests - API compatibility (for microservices)
For a complete production-ready test suite, consider this structure:
tests/
β
βββ unit/ # β
Unit tests (fast, isolated)
β βββ test_services.py
β βββ test_utils.py
β βββ test_models.py
β βββ test_repositories.py
β
βββ integration/ # β Integration tests
β βββ test_routes_users.py
β βββ test_routes_posts.py
β βββ test_db.py
β βββ test_dependencies.py
β
βββ e2e/ # β
End-to-end tests
β βββ test_full_api_flow.py
β βββ conftest.py
β
βββ acceptance/ # β
Acceptance tests (user journeys)
β βββ test_user_journey.py
β
βββ security/ # β Security tests
β βββ test_auth.py
β βββ test_authorization.py
β βββ test_input_validation.py
β
βββ performance/ # β Performance tests
β βββ test_benchmarks.py
β
βββ migrations/ # β Migration tests
β βββ test_alembic_migrations.py
β
βββ schema/ # β OpenAPI schema tests
β βββ test_openapi.py
β
βββ contract/ # β Contract tests
β βββ test_api_contracts.py
β
βββ conftest.py # Shared fixtures
# Run all tests with environment validation
./scripts/tests_start.sh
# Run all tests (without validation)
./scripts/test.sh
# Run specific test file
./scripts/test.sh tests/unit/test_user_service.py
# Run tests with pytest flags
./scripts/test.sh tests/unit -v --cov=app# Using script
./scripts/tests_start.sh
# Using make
make test
# Direct command
APP_ENV=test pytest# Unit tests only
pytest tests/unit/
./scripts/test.sh tests/unit/
# E2E tests only
pytest tests/e2e/
./scripts/test.sh tests/e2e/
# Acceptance tests only
pytest tests/acceptance/
./scripts/test.sh tests/acceptance/# Terminal output
pytest --cov=app --cov-report=term-missing
# HTML report
pytest --cov=app --cov-report=html
# Open htmlcov/index.html in browser
# XML report (for CI/CD)
pytest --cov=app --cov-report=xmlpytest tests/unit/test_user_service.pypytest tests/unit/test_user_service.py::TestUserService::test_get_user_by_id_success
pytest -k test_get_user_by_id_success# Run only unit tests
pytest -m unit
# Run only e2e tests
pytest -m e2e
# Skip slow tests
pytest -m "not slow"
# Run tests in parallel (requires pytest-xdist)
pytest -n autoTests automatically configure:
APP_ENV=testDB_PROVIDER=sqliteSQLITE_URL=sqlite+aiosqlite:///:memory:SECRET_KEY=test-secret-key-for-testing-purposes-only-min-32-chars
Main conftest.py provides:
db_session: Fresh database session for each testclient: FastAPI TestClient with database overrideasync_client: AsyncClient for async endpoint testing
Unit conftest.py provides:
mock_user_repository: Mocked UserRepositorymock_post_repository: Mocked PostRepositorymock_comment_repository: Mocked CommentRepositorymock_like_repository: Mocked LikeRepository
E2E conftest.py provides:
test_user_data: Sample user dataauthenticated_user: Pre-created authenticated userauth_headers: Authentication headers for requests
"""Unit tests for MyService."""
import pytest
from unittest.mock import AsyncMock
from app.services.my_service import MyService
@pytest.fixture
def my_service(mock_repository):
"""Create service with mocked repository."""
return MyService(mock_repository)
class TestMyService:
"""Test suite for MyService."""
@pytest.mark.asyncio
async def test_my_method_success(self, my_service, mock_repository):
"""Test successful method execution."""
mock_repository.get = AsyncMock(return_value={"id": 1})
result = await my_service.my_method(1)
assert result is not None
mock_repository.get.assert_called_once_with(1)"""E2E tests for MyAPI."""
import pytest
from fastapi import status
class TestMyAPI:
"""Test suite for MyAPI endpoints."""
def test_create_resource(self, client, auth_headers):
"""Test creating a resource."""
response = client.post(
"/api/v1/resources",
headers=auth_headers,
json={"name": "Test Resource"},
)
assert response.status_code == status.HTTP_201_CREATED
data = response.json()
assert data["name"] == "Test Resource""""Acceptance tests for user journey."""
import pytest
from fastapi import status
class TestUserJourney:
"""Test complete user journey."""
def test_complete_workflow(self, client):
"""Test complete workflow from start to finish."""
# Step 1: Register
register_response = client.post("/api/v1/users/register", json={...})
assert register_response.status_code == status.HTTP_201_CREATED
# Step 2: Login
login_response = client.post("/api/v1/auth/token", data={...})
assert login_response.status_code == status.HTTP_200_OK
# Step 3: Use authenticated endpoint
token = login_response.json()["access_token"]
headers = {"Authorization": f"Bearer {token}"}
resource_response = client.get("/api/v1/resources", headers=headers)
assert resource_response.status_code == status.HTTP_200_OKTests are organized with pytest markers:
@pytest.mark.unit: Unit tests@pytest.mark.e2e: End-to-end tests@pytest.mark.acceptance: Acceptance tests@pytest.mark.slow: Slow-running tests@pytest.mark.integration: Integration tests
- Follow TDD: Write tests before implementing features
- Isolation: Each test should be independent
- Clear Names: Use descriptive test names that explain what is being tested
- Arrange-Act-Assert: Structure tests clearly
- Mock External Dependencies: Unit tests should not hit real databases or APIs
- Test Edge Cases: Include tests for error conditions and boundary cases
- Keep Tests Fast: Unit tests should run in milliseconds
- Use Fixtures: Reuse common test setup via fixtures
- Document Complex Tests: Add docstrings explaining complex test scenarios
- Unit Tests: 90%+ coverage for services and repositories
- E2E Tests: Cover all API endpoints
- Acceptance Tests: Cover all major user journeys
Current coverage target: 80% (configured in pytest.ini)
- Ensure
APP_ENV=testis set - Check that SQLite is available (included in Python standard library)
- Ensure you're running tests from project root
- Check that
apppackage is in Python path
- Use
@pytest.mark.asynciofor async tests - Use
pytest-asyncioplugin (already in requirements-dev.txt)
- Check that fixture is defined in appropriate
conftest.py - Ensure fixture scope matches test needs
The project includes convenient scripts in the scripts/ folder for code quality checks:
# Fix linting issues automatically
./scripts/lint.sh
# Format code
./scripts/format.sh
# Fix linting and format in one command
./scripts/fix.sh
# Run all checks without modifying files (useful for CI)
./scripts/check.sh
# Run tests
./scripts/test.sh
# Run tests with environment validation
./scripts/tests_start.sh# Run linters
make lint
# Format code
make format
# Run type checker
make type-check
# Run all checks (lint + type-check)
make check
# Run tests
make test# Run linters
ruff check app scripts
mypy app/
# Format code
ruff format app scripts
# Run tests
APP_ENV=test pytestSee Development Workflow for more testing patterns and tests/README.md for detailed test documentation.
# Method 1: Using helper script (Recommended)
# Automatically loads the correct config file based on APP_ENV
./docker-compose.sh up -d # Uses dev.env (default)
APP_ENV=prod ./docker-compose.sh up -d # Uses prod.env
APP_ENV=staging ./docker-compose.sh up -d # Uses staging.env
APP_ENV=test ./docker-compose.sh up -d # Uses test.env
# Method 2: Using --env-file flag
APP_ENV=prod docker-compose --env-file configs/prod.env up -d
# Method 3: Export variables first (bash/zsh)
export APP_ENV=prod
set -a
source configs/prod.env
set +a
docker-compose up -d
# View logs
docker-compose logs -f
# View logs for specific service
docker-compose logs -f backend
# Stop services
docker-compose down
# Stop and remove volumes
docker-compose down -vEnvironment Configuration:
- The application loads configuration from files in the
configs/folder based onAPP_ENV:configs/dev.envwhenAPP_ENV=dev(default)configs/staging.envwhenAPP_ENV=stagingconfigs/prod.envwhenAPP_ENV=productionorAPP_ENV=prodconfigs/test.envwhenAPP_ENV=test
Important: When using a specific environment (e.g., prod), you must load the config file before running docker-compose so that environment variables are available to docker-compose itself (for service configuration).
Recommended: Use the helper script ./docker-compose.sh which automatically loads the correct config file:
APP_ENV=prod ./docker-compose.sh up -dThis ensures:
- The config file (
configs/prod.env) is loaded before docker-compose runs - Environment variables are available for docker-compose variable substitution
APP_ENVis set correctly for the application containers
Deploy to Render.com with automated CI/CD:
- Push to
mainbranch - GitHub Actions runs tests
- Builds Docker image
- Deploys to Render.com
- Runs migrations automatically
See Deployment Architecture for complete deployment guide.
This project includes a comprehensive CI/CD pipeline using GitHub Actions that automatically tests every commit and pull request.
The CI workflow (.github/workflows/ci.yml) runs on every push to main or develop branches and on all pull requests:
Jobs:
- Lint Code - Validates code style with
ruff - Type Check - Ensures type safety with
mypy - Test (Python 3.12 & 3.13) - Runs comprehensive test suites across multiple Python versions
- Unit tests (with coverage)
- Integration tests
- End-to-end tests
- Acceptance tests
- Test Coverage Report - Generates detailed coverage reports (requires 80% minimum)
Services:
- PostgreSQL 17 (primary database)
- MongoDB 7 (document storage)
- Qdrant (vector database)
- Redis 7 (caching/task queue)
Duration: ~5-10 minutes per run
The CI workflow supports optional secrets for external services (Supabase, Mailgun, B2, Sentry). To configure:
- Go to Repository Settings β Secrets and variables β Actions
- Add required secrets (see .github/SETUP_SECRETS.md for quick setup)
Minimum required secrets:
TEST_SECRET_KEY # Generate: python -c "import secrets; print(secrets.token_urlsafe(32))"
TEST_JWT_SECRET_KEY # Generate: python -c "import secrets; print(secrets.token_urlsafe(32))"Optional secrets for full functionality:
TEST_SUPABASE_URL,TEST_SUPABASE_KEY,TEST_SUPABASE_DB_PASSWORDTEST_MAILGUN_API_KEY,TEST_MAILGUN_DOMAINTEST_B2_KEY_ID,TEST_B2_APPLICATION_KEYTEST_SENTRY_DSN
See .github/SECRETS.md for detailed secrets documentation.
- Actions Tab: View all workflow runs and logs
- Pull Requests: See CI status checks before merging
- Coverage Reports: Download HTML coverage reports from workflow artifacts
Run the same checks locally before pushing:
# Run all checks (lint, type-check, tests)
make check && make test
# Individual checks
make lint # Code style check
make type-check # Type safety check
make test # Run all tests
make test-coverage # Generate coverage reportThis template follows:
- Clean Architecture - Dependency inversion, clear layer boundaries
- Domain-Driven Design - Business logic at the core
- SOLID Principles - Single responsibility, open/closed, etc.
- Type Safety - Full type hints with mypy strict mode
- RORO Pattern - Receive Object, Return Object for all endpoints
- Modern Dependency Injection - Using
Annotated[Type, Depends(...)]pattern (Pydantic v2 & FastAPI best practices) - Testability - Dependency injection enables easy unit testing
Note: All dependencies use the modern Annotated pattern for better type safety and alignment with Pydantic v2 conventions. See API Design Documentation for details.
See Architecture Overview for details.
- FastAPI - Modern async web framework
- Pydantic v2 - Data validation with type safety
- SQLAlchemy 2.0 - Async ORM with modern patterns
- Alembic - Database migrations
- Annotated Dependencies - Modern dependency injection using
Annotated[Type, Depends(...)]pattern
- User - Authentication, profile, email confirmation
- Post - User-generated content with optional images
- Comment - Comments on posts
- Like - Post likes with count tracking
- PostgreSQL (Supabase) - Primary relational database
- Database Provider Selection - Configure via
DB_PROVIDER(supabase, postgresql, sqlite) - SQLite - Development/testing
- MongoDB - Document storage
- Qdrant - Vector database for semantic search
- Docker & Docker Compose - Containerization
- Render.com - Production hosting
- GitHub Actions - CI/CD pipeline
- Supabase - Managed PostgreSQL + Auth
- Pytest - Testing framework with async support
- Ruff - Linter and formatter
- Mypy - Static type checker (strict mode)
- Structlog - Structured logging with correlation IDs
- asgi-correlation-id - Request correlation tracking
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Write tests for your changes
- Ensure all tests pass:
./scripts/tests_start.sh # or make test
- Run code quality checks:
./scripts/fix.sh # Auto-fix linting and format code ./scripts/check.sh # Verify all checks pass # or make format # Format code make lint # Check linting make check # Run all checks (lint + type-check)
- Commit your changes (
git commit -m 'feat: add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
See Development Workflow for detailed contribution guidelines.
This project includes a comprehensive Makefile for common development tasks. Run make help to see all available commands.
# All-in-one: Initialize DB, format, lint, and test (RECOMMENDED for first-time setup)
make all ENV=dev
# This single command does everything:
# 1. Waits for database to be ready
# 2. Runs database migrations
# 3. Creates initial data (superuser)
# 4. Formats code with ruff
# 5. Lints code
# 6. Runs all testsmake install # Install dependencies using uv
make set-env ENV=dev # Set environment (dev|staging|production|test)# Database initialization (complete setup)
make init-db ENV=dev # Wait for DB, run migrations, create initial data
# Migration management
make migrate ENV=dev # Run all pending migrations (upgrade head)
make migrate-current ENV=dev # Show current migration revision
make migrate-history ENV=dev # Show migration history
make migrate-revision MESSAGE='add user table' ENV=dev # Create new migration
make migrate-upgrade REVISION=<rev> ENV=dev # Upgrade to specific revision
make migrate-downgrade REVISION=-1 ENV=dev # Downgrade by one revisionImportant: The Makefile automatically uses -c app/alembic.ini for all Alembic commands. If running Alembic directly, always specify the config:
python -m alembic -c app/alembic.ini upgrade head
python -m alembic -c app/alembic.ini currentmake format # Format code with ruff (auto-fix)
make lint # Run ruff linter (check only)
make type-check # Run mypy type checker
make check # Run all checks (lint + type-check)Tip: Use make format before committing to ensure consistent code style.
make test # Run all tests
make test-unit # Run unit tests only (fast, isolated)
make test-integration # Run integration tests only
make test-e2e # Run end-to-end tests only
make test-acceptance # Run acceptance tests only
make test-coverage # Run tests with coverage reportTest Workflow:
- Write tests first (TDD approach)
- Run
make test-unitfor fast feedback during development - Run
make testbefore committing - Run
make test-coverageto check coverage
make dev # Run in development mode (auto-reload)
make staging # Run in staging environment
make prod # Run in production environment# Build
make docker-build # Build default Docker image
make docker-build-env ENV=dev # Build for specific environment
# Run
make docker-run # Run with default (dev) environment
make docker-run-env ENV=dev # Run for specific environment
make docker-logs ENV=dev # View logs
make docker-stop ENV=dev # Stop containers
# Docker Compose (full stack)
make docker-compose-up ENV=dev # Start entire stack
make docker-compose-down ENV=dev # Stop entire stack
make docker-compose-logs ENV=dev # View all service logsmake clean # Remove generated files (venv, cache, coverage)make help # Show all available commands with descriptionsmake install
cp configs/dev.env.example configs/dev.env
# Edit configs/dev.env
docker-compose up -d
make all ENV=dev
make dev# Start working
make dev
# Before committing
make format # Format code
make lint # Check for issues
make test # Run testsmake migrate-revision MESSAGE='add new feature' ENV=dev
# Review the generated migration file
make migrate ENV=dev # Apply migrationmake format # Format code
make lint # Check linting
make type-check # Check types
make test # Run all tests
make test-coverage # Check coverageThis project is licensed under the MIT License.
- Explore the Documentation
- Understand the Architecture
- Set up your Development Environment
- Test the POC features:
- Register a new user
- Confirm email
- Create posts
- Add comments
- Like posts
- Build following Best Practices
- Deploy using the Deployment Guide
This is a Proof of Concept demonstrating:
- β User authentication and registration workflow
- β Email confirmation system
- β Post creation and management
- β Comment system
- β Like/unlike functionality
- β Clean architecture patterns
- β Type-safe API with Pydantic v2
- β Multi-database provider support
Note: This POC is built on an enterprise-grade FastAPI template and can be extended for production use.
Built with β€οΈ for production-ready FastAPI applications