A production-ready FastAPI template with modern Python tooling, async database operations, authentication system, and Docker support.
- FastAPI Framework: High-performance async web framework
- SQLModel Integration: Type-safe database operations with SQLAlchemy 2.0
- Authentication System: Complete session-based authentication with role-based permissions
- Database Operations: Full synchronous support with Session
- Alembic Migrations: Database schema versioning and migrations
- Email Services: SMTP email sending with HTML template support
- Docker Support: Multi-stage Dockerfile with Docker Compose
- Rich Logging: Colorized console logging with file output
- CORS Middleware: Cross-origin resource sharing configuration
- Environment Configuration: Flexible environment variable management
- Code Quality: Ruff formatting and linting configuration
- Security Framework: Role-based permissions and security utilities
- Modern Python: Python 3.13+ with UV package manager
fastapi-template/
βββ app/ # Main application package
β βββ api/ # API layer
β β βββ routes/
β β βββ v1/ # API version 1
β β βββ controllers/ # Route handlers (auth.py, hello_world.py)
β β βββ dto/ # Data Transfer Objects (request/response models)
β β βββ providers/ # Business logic providers
β β βββ router.py # Main API router
β βββ core/ # Core application logic
β β βββ config/
β β β βββ env.py # Environment configuration
β β βββ db/
β β β βββ models.py # SQLModel database models
β β β βββ setup.py # Async database setup and connection
β β β βββ utils.py # Database utilities
β β β βββ builders/ # Model builders (role.py, permission.py)
β β βββ logging/
β β β βββ log.py # Rich logging utilities
β β βββ security/ # Security framework
β β β βββ checkers.py # Security validation functions
β β β βββ permissions.py # Permission constants and utilities
β β βββ services/
β β βββ email.py # Email service
β β βββ templating.py # Jinja2 template rendering
β βββ utils/ # Utility functions
β β βββ crypto.py # Cryptographic utilities
β β βββ date.py # Date utilities
β βββ app.py # FastAPI application factory
βββ migrations/ # Alembic migration files
β βββ env.py # Alembic environment configuration
β βββ versions/ # Migration versions
βββ docker-compose.yml # Docker Compose configuration
βββ Dockerfile # Multi-stage Docker build
βββ main.py # Application entry point
βββ pyproject.toml # Project configuration and dependencies
βββ ruff.toml # Code formatting and linting rules
βββ alembic.ini # Alembic configuration
βββ .env.example # Environment variables template
- Python 3.13+
- PostgreSQL (for production)
- UV package manager (recommended)
-
Clone the repository
git clone https://github.com/Frusadev/fastapi-template.git cd fastapi-template
-
Set up environment
# Copy environment template cp .env.example .env # Edit .env with your configuration nano .env
-
Install dependencies
# Using UV (recommended) uv sync # Or using pip pip install -e .
-
Set up database
# Initialize Alembic (if needed) alembic init migrations # Create migration alembic revision --autogenerate -m "Initial migration" # Apply migrations alembic upgrade head
-
Run the application
python main.py
The API will be available at http://localhost:8000
-
Using Docker Compose
# Start all services (app + PostgreSQL) docker-compose up --build # Run in background docker-compose up -d --build
-
Using Docker only
# Build image docker build -t fastapi-template . # Run container docker run -p 8000:8000 --env-file .env fastapi-template
Configure your application using the .env
file:
# Database Configuration
DB_STRING="postgresql+asyncpg://username:password@db:5432/yourdb"
ALEMBIC_DB_URL="postgresql+psycopg2://username:password@db:5432/yourdb"
# Application Configuration
DEBUG=True
PORT=8000
# Email Configuration
EMAIL_APP_PASSWORD="your-app-password"
APP_EMAIL_ADDRESS="[email protected]"
EMAIL_TEMPLATES_PATH="assets/templates/email/"
The template supports multiple database backends through SQLModel:
- PostgreSQL (recommended for production)
- SQLite (for development/testing)
Update the DB_STRING
in your .env
file according to your database choice.
The template uses Session for all database operations, providing reliable and straightforward database access:
from typing import Annotated
from fastapi import Depends
from sqlmodel import Session
from app.core.db.setup import create_db_session
@router.get("/users")
def get_users(
db_session: Annotated[Session, Depends(create_db_session)],
):
# Execute query
result = db_session.exec(select(User))
users = result.all()
return {"users": users}
@router.post("/users")
def create_user(
user_data: UserCreateDTO,
db_session: Annotated[Session, Depends(create_db_session)],
):
user = User(**user_data.model_dump())
db_session.add(user)
db_session.commit() # Synchronous commit
db_session.refresh(user) # Synchronous refresh
return user
Key patterns for database operations:
# 1. Querying data
def get_user_by_email(db_session: Session, email: str) -> User | None:
result = db_session.exec(select(User).where(User.email == email))
return result.first()
# 2. Creating records
def create_user(db_session: Session, user_data: dict) -> User:
user = User(**user_data)
db_session.add(user)
db_session.commit()
db_session.refresh(user)
return user
# 3. Complex queries with relationships
def get_user_with_roles(db_session: Session, user_id: str) -> User | None:
result = db_session.exec(
select(User)
.options(selectinload(User.roles)) # Eager load relationships
.where(User.id == user_id)
)
return result.first()
# 4. Transactions
def transfer_operation(db_session: Session):
try:
# Multiple operations in transaction
db_session.add(record1)
db_session.add(record2)
db_session.commit()
except Exception:
db_session.rollback()
raise
Define your database models in app/core/db/models.py
using SQLModel:
from datetime import datetime, timezone
from sqlmodel import SQLModel, Field, Relationship
from typing import Optional
class User(SQLModel, table=True):
id: str = Field(default_factory=lambda: gen_id(10), primary_key=True)
email: str = Field(unique=True, index=True)
username: str = Field(unique=True, index=True)
name: str
hashed_password: str
# Relationships (async compatible)
login_sessions: list["LoginSession"] = Relationship(back_populates="user")
roles: list["Role"] = Relationship(back_populates="users", link_model=RoleUserLink)
class LoginSession(SQLModel, table=True):
id: str = Field(default_factory=lambda: gen_id(30), primary_key=True)
user_id: str = Field(foreign_key="user.id")
expires_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc) + timedelta(days=60))
expired: bool = False
# Async relationship
user: User = Relationship(back_populates="login_sessions")
# Create a new migration
alembic revision --autogenerate -m "Description of changes"
# Apply migrations
alembic upgrade head
# Rollback to previous migration
alembic downgrade -1
# View migration history
alembic history
The template includes a comprehensive email service with template support:
from app.core.services.email import send_email
send_email(
email="[email protected]",
subject="Welcome!",
message="Welcome to our service!",
html=False
)
from app.core.services.email import send_templated_email
send_templated_email(
email="[email protected]",
subject="Welcome!",
template_name="welcome",
context={"name": "John Doe", "app_name": "MyApp"},
fallback_message="Welcome to our service!"
)
Create HTML templates in your configured templates directory:
<!-- assets/templates/email/welcome.html -->
<!DOCTYPE html>
<html>
<head>
<title>Welcome to {{ app_name }}</title>
</head>
<body>
<h1>Welcome, {{ name }}!</h1>
<p>Thank you for joining {{ app_name }}.</p>
</body>
</html>
The template includes a rich logging system with colored console output and file logging:
from app.core.logging.log import log_info, log_warning, log_error, log_success
log_info("Application started")
log_warning("This is a warning")
log_error("An error occurred")
log_success("Operation completed successfully")
The template includes a complete session-based authentication system with role-based permissions.
- User Registration: Create account with email, username, password
- Login: Authenticate and create session cookie
- Session Management: HTTP-only cookies for security
- Role-based Access: Automatic role and permission assignment
- Logout: Clear session and invalidate cookies
The authentication system provides these endpoints:
POST /api/v1/auth/register # Register new user
POST /api/v1/auth/login # Login user
GET /api/v1/auth/me # Get current user info
POST /api/v1/auth/logout # Logout user
from typing import Annotated
from fastapi import Depends
from app.api.routes.v1.providers.auth import get_current_user
from app.core.db.models import User
@router.get("/protected-route")
def protected_endpoint(
current_user: Annotated[User, Depends(get_current_user)],
):
# User is automatically authenticated via session cookie
return {"message": f"Hello {current_user.name}!"}
# For WebSocket authentication
from app.api.routes.v1.providers.auth import ws_get_current_user
@app.websocket("/ws")
def websocket_endpoint(
websocket: WebSocket,
current_user: User = Depends(ws_get_current_user)
):
# WebSocket with authentication
websocket.accept()
# ... websocket logic
1. Modify User Model (app/core/db/models.py
):
class User(SQLModel, table=True):
# Add custom fields
profile_image: str | None = None
is_verified: bool = False
created_at: datetime = Field(default_factory=datetime.utcnow)
# Add custom relationships
posts: list["Post"] = Relationship(back_populates="author")
2. Extend Registration (app/api/routes/v1/providers/auth.py
):
def register(
db_session: Session,
username: str,
email: EmailStr,
password: str,
password_confirm: str,
name: str,
# Add custom fields
profile_image: str | None = None,
):
# Your custom validation logic
check_custom_validation(email)
# Create user with custom fields
user = User(
username=username,
email=email,
hashed_password=hash_password(password),
name=name,
profile_image=profile_image,
)
# Custom role assignment logic
role = create_custom_role(user)
# ... rest of logic
3. Custom DTOs (app/api/routes/v1/dto/auth.py
):
class RegisterRequestDTO(BaseModel):
username: str
email: EmailStr
password: str = Field(min_length=8) # Add validation
password_confirm: str
name: str
profile_image: str | None = None # Add custom fields
class UserResponseDTO(BaseModel):
id: str
username: str
email: str
name: str
is_verified: bool
created_at: datetime
# Add any custom fields you want to expose
The template includes a flexible role-based permission system:
# Check permissions in your routes
from app.core.security.permissions import ACTION_READWRITE, USER_RESOURCE
async def check_user_permission(current_user: User, resource_id: str):
# Permission checking logic (implement as needed)
for role in current_user.roles:
for permission in role.permissions:
if (permission.name == ACTION_READWRITE and
permission.resource_name == USER_RESOURCE):
return True
return False
Customize session behavior in the auth provider:
# In login function - customize session duration
login_session = LoginSession(
user_id=user.id,
expires_at=datetime.now(timezone.utc) + timedelta(days=30) # Custom duration
)
# Cookie settings
response.set_cookie(
key="user_session_id",
value=login_session.id,
httponly=True,
secure=True, # HTTPS only in production
samesite="strict", # CSRF protection
expires=login_session.expires_at.astimezone(timezone.utc),
)
The template includes secure utilities for generating IDs and one-time passwords:
from app.utils.crypto import gen_id, gen_otp
# Generate secure URL-safe ID
user_id = gen_id(32) # Returns 32-character URL-safe string
# Generate numeric OTP
otp_code = gen_otp(6) # Returns 6-digit numeric string
The template follows a clean architecture pattern with separation of concerns:
Controllers (Routes) β Providers (Business Logic) β Models (Database)
β
DTOs (Data Transfer)
1. Create DTOs (app/api/routes/v1/dto/
):
# app/api/routes/v1/dto/todo.py
from pydantic import BaseModel
from datetime import datetime
class TodoCreateDTO(BaseModel):
title: str
description: str | None = None
priority: int = 1
class TodoResponseDTO(BaseModel):
id: str
title: str
description: str | None
completed: bool
created_at: datetime
user_id: str
class Config:
from_attributes = True
2. Create Provider (app/api/routes/v1/providers/
):
# app/api/routes/v1/providers/todo.py
from sqlmodel import Session
from sqlmodel import select
from app.core.db.models import User, Todo
def create_todo(
db_session: Session,
current_user: User,
title: str,
description: str | None = None,
priority: int = 1,
) -> Todo:
todo = Todo(
title=title,
description=description,
priority=priority,
user_id=current_user.id,
)
db_session.add(todo)
db_session.commit()
db_session.refresh(todo)
return todo
def get_user_todos(
db_session: Session,
current_user: User,
) -> list[Todo]:
result = db_session.exec(
select(Todo).where(Todo.user_id == current_user.id)
)
return result.all()
3. Create Controller (app/api/routes/v1/controllers/
):
# app/api/routes/v1/controllers/todo.py
from typing import Annotated
from fastapi import APIRouter, Depends
from sqlmodel.ext.asyncio.session import AsyncSession
from app.api.routes.v1.dto.todo import TodoCreateDTO, TodoResponseDTO
from app.api.routes.v1.providers.auth import get_current_user
from app.api.routes.v1.providers.todo import create_todo, get_user_todos
from app.core.db.models import User
from app.core.db.setup import create_db_session
router = APIRouter(prefix="/todos", tags=["Todos"])
@router.post("/", response_model=TodoResponseDTO)
def create_user_todo(
request: TodoCreateDTO,
current_user: Annotated[User, Depends(get_current_user)],
db_session: Annotated[Session, Depends(create_db_session)],
):
"""Create a new todo for the authenticated user."""
todo = create_todo(
db_session=db_session,
current_user=current_user,
title=request.title,
description=request.description,
priority=request.priority,
)
return todo
@router.get("/", response_model=list[TodoResponseDTO])
def get_my_todos(
current_user: Annotated[User, Depends(get_current_user)],
db_session: Annotated[Session, Depends(create_db_session)],
):
"""Get all todos for the authenticated user."""
todos = get_user_todos(db_session, current_user)
return todos
4. Register Router (app/api/routes/v1/router.py
):
from app.api.routes.v1.controllers.todo import router as todo_router
router.include_router(todo_router)
After setup, your API will have these endpoints:
# Authentication
POST /api/v1/auth/register # Register new user
POST /api/v1/auth/login # Login user
GET /api/v1/auth/me # Get current user
POST /api/v1/auth/logout # Logout user
# Hello World Examples
GET /api/v1/hello/ # Simple greeting
GET /api/v1/hello/greeting # Time-based greeting
GET /api/v1/hello/user-greeting # Authenticated user greeting
GET /api/v1/hello/info # API information
GET /api/v1/hello/stats # User statistics
# Your custom endpoints...
GET /api/v1/todos/ # Get user todos
POST /api/v1/todos/ # Create todo
FastAPI automatically generates interactive documentation:
- Swagger UI:
http://localhost:8000/docs
- ReDoc:
http://localhost:8000/redoc
- OpenAPI JSON:
http://localhost:8000/openapi.json
The template includes comprehensive error handling through security checkers:
from app.core.security.checkers import (
check_existence,
check_conditions,
check_equality,
check_non_existence,
)
# Usage in providers
def get_todo_by_id(db_session: Session, todo_id: str, user_id: str):
todo = check_existence(
db_session.get(Todo, todo_id),
detail="Todo not found"
)
check_conditions(
[todo.user_id == user_id],
detail="Access denied"
)
return todo
The template is ready for testing with pytest:
# Install test dependencies
pip install pytest pytest-asyncio httpx
# Run tests
pytest
The project uses Ruff for code formatting and linting:
# Format code
ruff format
# Check for linting issues
ruff check
# Fix auto-fixable issues
ruff check --fix
The template is optimized for UV, a fast Python package manager:
# Add new dependency
uv add package-name
# Add development dependency
uv add --dev package-name
# Update dependencies
uv sync --upgrade
- FastAPI[standard]: Web framework with all standard features
- SQLModel: Database ORM with type safety
- Alembic: Database migration tool
- AsyncPG: Async PostgreSQL driver
- Psycopg2-binary: PostgreSQL adapter
- Uvloop: High-performance event loop
- Rich: Rich console output
- SlowAPI: Rate limiting
- Passlib[bcrypt]: Password hashing
- Piccolo: Additional database tools
- WebSockets: WebSocket support
- Python-dotenv: Environment variable loading
- Jinja2: Template engine (for email templates)
The template includes a complete Docker setup with multi-stage builds and database integration.
# 1. Clone and navigate to the project
git clone https://github.com/yourusername/fastapi-template.git
cd fastapi-template
# 2. Copy environment file
cp .env.example .env
# 3. Start the entire stack
docker-compose up --build
# The API will be available at http://localhost:8000
# PostgreSQL will be available at localhost:5432
docker-compose.yml includes:
services:
# FastAPI Application
app:
build: .
ports:
- "8000:8000"
environment:
- DB_STRING=postgresql+asyncpg://postgres:password@db:5432/fastapi_db
depends_on:
- db
volumes:
- ./app:/app/app # Hot reload in development
# PostgreSQL Database
db:
image: postgres:15
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: fastapi_db
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
The Dockerfile is optimized for production with multiple stages:
# Builder stage - Install dependencies
FROM python:3.13-slim as builder
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN pip install uv && uv sync --frozen
# Production stage - Minimal runtime
FROM python:3.13-slim
WORKDIR /app
COPY --from=builder /app/.venv /app/.venv
COPY . .
EXPOSE 8000
CMD ["/app/.venv/bin/python", "main.py"]
Development (with hot reload):
# Start with development overrides
docker-compose -f docker-compose.yml -f docker-compose.dev.yml up
# Or use volume mounts for hot reload
docker-compose up --build
Production deployment:
# Build production image
docker build -t fastapi-template:prod .
# Run with production settings
docker run -d \
--name fastapi-app \
-p 8000:8000 \
--env-file .env.prod \
fastapi-template:prod
Run migrations in the container:
# Run migrations
docker-compose exec app alembic upgrade head
# Create new migration
docker-compose exec app alembic revision --autogenerate -m "Add new table"
# Check migration status
docker-compose exec app alembic current
Create .env
file for Docker Compose:
# Database (matches docker-compose.yml)
DB_STRING=postgresql+asyncpg://postgres:password@db:5432/fastapi_db
ALEMBIC_DB_URL=postgresql+psycopg2://postgres:password@db:5432/fastapi_db
# Application
DEBUG=True
PORT=8000
# Email (configure with your SMTP)
EMAIL_APP_PASSWORD=your-app-password
[email protected]
- Multi-stage builds - Smaller production images
- Non-root user - Security best practices
- Layer caching - Faster rebuilds
- Volume persistence - Database data survives container restarts
- Health checks - Container health monitoring
- Minimal base images - Reduced attack surface
The env.py
module provides type-safe environment variable access:
from app.core.config.env import get_env
# Get environment variable with default
db_url = get_env("DB_STRING", "sqlite:///./test.db")
# Type-safe environment keys
debug = get_env("DEBUG", "False") == "True"
# Clone the template
git clone https://github.com/yourusername/fastapi-template.git my-project
cd my-project
# Set up environment
cp .env.example .env
# Edit .env with your database and email settings
# Install dependencies
uv sync
# Run database migrations
alembic upgrade head
# Start the development server
python main.py
# Register a new user
curl -X POST "http://localhost:8000/api/v1/auth/register" \
-H "Content-Type: application/json" \
-d '{
"username": "testuser",
"email": "[email protected]",
"password": "password123",
"password_confirm": "password123",
"name": "Test User"
}'
# Login
curl -X POST "http://localhost:8000/api/v1/auth/login" \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"password": "password123"
}'
# Access protected endpoint (after login)
curl -X GET "http://localhost:8000/api/v1/auth/me" \
--cookie-jar cookies.txt --cookie cookies.txt
Modify User Model for your requirements:
# In app/core/db/models.py
class User(SQLModel, table=True):
# Keep existing fields...
# Add your custom fields
phone: str | None = None
company: str | None = None
avatar_url: str | None = None
is_premium: bool = False
subscription_ends: datetime | None = None
Create Your Business Logic:
- Add new models in
app/core/db/models.py
- Create DTOs in
app/api/routes/v1/dto/
- Write providers in
app/api/routes/v1/providers/
- Build controllers in
app/api/routes/v1/controllers/
- Register routes in
app/api/routes/v1/router.py
# In app/core/db/models.py
class Product(SQLModel, table=True):
id: str = Field(default_factory=lambda: gen_id(12), primary_key=True)
name: str = Field(index=True)
description: str | None = None
price: float
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
# Foreign key relationship
user_id: str = Field(foreign_key="user.id")
user: User = Relationship()
Create custom email templates in your templates directory:
<!-- templates/welcome.html -->
<!DOCTYPE html>
<html>
<head>
<style>
.container { max-width: 600px; margin: 0 auto; font-family: Arial, sans-serif; }
.header { background: #007bff; color: white; padding: 20px; text-align: center; }
.content { padding: 20px; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Welcome to {{ app_name }}!</h1>
</div>
<div class="content">
<p>Hi {{ name }},</p>
<p>Thank you for joining {{ app_name }}. We're excited to have you!</p>
<p>Your account details:</p>
<ul>
<li>Email: {{ email }}</li>
<li>Username: {{ username }}</li>
</ul>
</div>
</div>
</body>
</html>
Extend the permission system:
# In app/core/security/permissions.py
# Add your custom permissions
PRODUCT_RESOURCE = "product"
ORDER_RESOURCE = "order"
ACTION_CREATE = "create"
ACTION_UPDATE = "update"
ACTION_DELETE = "delete"
# In your providers
def check_product_permission(user: User, action: str, product_id: str):
# Your permission logic
pass
Add custom environment variables:
# In app/core/config/env.py or create your own config module
STRIPE_SECRET_KEY = get_env("STRIPE_SECRET_KEY")
REDIS_URL = get_env("REDIS_URL", "redis://localhost:6379")
AWS_BUCKET_NAME = get_env("AWS_BUCKET_NAME")
# Build production image
docker build -t my-app:latest .
# Run with production settings
docker run -d \
--name my-app \
-p 8000:8000 \
--env-file .env.production \
--restart unless-stopped \
my-app:latest
# .env.production
DEBUG=False
DB_STRING=postgresql+asyncpg://user:pass@prod-db:5432/myapp
EMAIL_APP_PASSWORD=your-production-email-password
[email protected]
CORS_ORIGINS=https://yourdomain.com,https://www.yourdomain.com
The template is ready for health check endpoints:
# Add to your router
@router.get("/health")
def health_check():
return {"status": "healthy", "timestamp": datetime.utcnow()}
This template is provided as-is for educational and development purposes.