This guide provides comprehensive information for developers working on the fastapi-base project, including setup instructions, development workflows, and best practices.
Before starting development, ensure you have the following installed:
- Docker and Docker Compose (for containerized development)
- Python 3.13+ (for local development)
- uv - Fast Python package manager (installation guide)
- Git - Version control system
- Make - Build automation tool (usually pre-installed on Unix systems)
-
Clone and enter the repository:
git clone https://github.com/GabrielVGS/fastapi-base.git cd fastapi-base -
Copy environment configuration:
cp .env.example .env
-
Install Python dependencies (for local development):
cd fastapi-base/ uv sync -
Install pre-commit hooks:
make hooks
-
Start the development environment:
make build
The easiest way to get started is using Docker Compose:
# Build and start all services
make build
# Or just start services (if already built)
make upThis will start:
- FastAPI application on
http://localhost:8666 - PostgreSQL database on
localhost:5431 - Redis for caching and sessions
- Celery workers for background tasks
- Celery beat for periodic tasks
# Stop all services
make down
# Access the application container
make bash
# View logs
docker compose logs -f fastapi-base
# Rebuild specific service
docker compose up --build fastapi-baseEdit the .env file to configure your development environment:
# Development settings
DEBUG=True
ENV=dev
# Database (using Docker defaults)
POSTGRES_USER=test
POSTGRES_PASSWORD=test
POSTGRES_DB=test
POSTGRES_HOST=db
POSTGRES_PORT=5432
# Redis (using Docker defaults)
REDIS_HOST=redis
REDIS_PORT=6379The project uses Alembic for database migrations:
For a fresh database:
make alembic-init # Create initial migration
make alembic-migrate # Apply migrationsWhen you modify models:
make alembic-make-migrations "describe your changes"
make alembic-migrate# Create a new migration
make alembic-make-migrations "add user table"
# Apply pending migrations
make alembic-migrate
# Reset database (careful in production!)
make alembic-reset
# View migration history
docker compose exec fastapi-base alembic history
# Downgrade to previous migration
docker compose exec fastapi-base alembic downgrade -1When creating new models, follow these guidelines:
# Example model in src/models/user.py
from sqlmodel import SQLModel, Field
from typing import Optional
from datetime import datetime
from .base import BaseModel
class UserBase(BaseModel):
email: str
full_name: Optional[str] = None
is_active: bool = Field(default=True)
class User(UserBase, table=True):
hashed_password: str
class UserCreate(UserBase):
password: str
class UserRead(UserBase):
# All fields from BaseModel (id, created_at, updated_at, deleted_at)
# + email, full_name, is_active from UserBase
pass- Always create migrations for schema changes
- Use descriptive migration names that explain the changes
- Test migrations on a copy of production data
- Never edit existing migrations that have been applied
- Use indexes for frequently queried fields
- Define proper relationships between models
# Run all tests with coverage
make test
# Run specific test file
docker compose exec fastapi-base pytest tests/test_api/test_users.py
# Run tests with verbose output
docker compose exec fastapi-base pytest -v
# Run tests matching a pattern
docker compose exec fastapi-base pytest -k "test_user"Organize tests in the tests/ directory:
tests/
βββ conftest.py # Shared fixtures and configuration
βββ test_api/ # API endpoint tests
β βββ test_auth.py # Authentication tests
β βββ test_users.py # User API tests
βββ test_models/ # Model tests
β βββ test_user.py # User model tests
βββ test_services/ # Business logic tests
β βββ test_user_service.py
βββ test_utils/ # Utility function tests
βββ test_helpers.py
The project uses pre-commit hooks to ensure code quality:
# Install hooks
make hooks
# Run hooks manually
make precommit-run
# Run specific hook
pre-commit run black
pre-commit run ruff- Follow PEP 8 style guidelines
- Use type hints for all functions and methods
- Write docstrings for public APIs
- Keep functions small and focused
- Use meaningful variable names
- Add comments for complex logic
Example of well-formatted code:
from typing import Optional, List
from sqlmodel import Session, select
async def get_user_by_email(
db: Session,
email: str
) -> Optional[User]:
"""Retrieve a user by their email address.
Args:
db: Database session
email: User's email address
Returns:
User object if found, None otherwise
"""
statement = select(User).where(User.email == email)
result = db.exec(statement)
return result.first()
async def get_active_users(
db: Session,
skip: int = 0,
limit: int = 100
) -> List[User]:
"""Retrieve a list of active users with pagination.
Args:
db: Database session
skip: Number of records to skip
limit: Maximum number of records to return
Returns:
List of active users
"""
statement = (
select(User)
.where(User.is_active == True)
.offset(skip)
.limit(limit)
)
result = db.exec(statement)
return result.all()-
Create a feature branch:
git checkout -b feature/user-authentication
-
Make your changes following the coding standards
-
Write tests for new functionality
-
Run quality checks:
make precommit-run make test -
Commit your changes:
git add . git commit -m "feat(auth): implement JWT authentication"
-
Push and create PR:
git push origin feature/user-authentication
When adding new API endpoints:
- Define the schema in
src/schemas/ - Create the endpoint in
src/api/v1/endpoints/ - Add business logic in
src/services/ - Write tests in
tests/test_api/ - Update documentation if needed
Example API endpoint:
# src/api/v1/endpoints/users.py
from fastapi import APIRouter, Depends, HTTPException
from sqlmodel import Session
from src.api.deps import get_current_user, get_session
from src.schemas.user import UserCreate, UserRead
from src.services.user_service import UserService
router = APIRouter()
@router.post("/", response_model=UserRead)
async def create_user(
user_in: UserCreate,
db: Session = Depends(get_session)
):
"""Create a new user."""
user_service = UserService(db)
# Check if user already exists
if user_service.get_by_email(user_in.email):
raise HTTPException(
status_code=400,
detail="User with this email already exists"
)
user = user_service.create(user_in)
return user
@router.get("/me", response_model=UserRead)
async def get_current_user_info(
current_user: User = Depends(get_current_user)
):
"""Get current user information."""
return current_userFor Celery tasks, create them in src/tasks/:
# src/tasks/email_tasks.py
from celery import Celery
from src.core.config import settings
celery_app = Celery("fastapi-base")
@celery_app.task
def send_welcome_email(user_email: str, user_name: str) -> str:
"""Send welcome email to new user."""
# Email sending logic here
print(f"Sending welcome email to {user_email}")
return f"Welcome email sent to {user_name}"
@celery_app.task
def cleanup_expired_sessions():
"""Clean up expired user sessions."""
# Cleanup logic here
print("Cleaning up expired sessions")
return "Session cleanup completed"For debugging the application:
# Run with debugger
docker compose exec fastapi-base python -m debugpy --listen 0.0.0.0:5678 --wait-for-client -m uvicorn src.main:app --reload --host 0.0.0.0 --port 8000
# Or run locally
cd fastapi-base/
uv run python -m debugpy --listen 0.0.0.0:5678 --wait-for-client -m uvicorn src.main:app --reload# Connect to PostgreSQL
docker compose exec db psql -U test -d test
# View database tables
docker compose exec db psql -U test -d test -c "\dt"
# Check migration status
docker compose exec fastapi-base alembic current
# View migration history
docker compose exec fastapi-base alembic history# View application logs
docker compose logs -f fastapi-base
# View database logs
docker compose logs -f db
# View Celery worker logs
docker compose logs -f celery- Use database indexes for frequently queried fields
- Optimize queries with proper joins and filters
- Use connection pooling for better resource management
- Monitor slow queries and optimize them
- Use async/await for I/O operations
- Implement caching for frequently accessed data
- Add pagination for large datasets
- Use compression for large responses
# Example caching with Redis
from src.utils.cache import cache_manager
@cache_manager.cache(expire=300) # 5 minutes
async def get_user_profile(user_id: int) -> dict:
"""Get user profile with caching."""
# Database query here
return user_data- Use JWT tokens for stateless authentication
- Hash passwords using bcrypt
- Validate all inputs using Pydantic schemas
- Implement rate limiting for API endpoints
- Use parameterized queries to prevent SQL injection
- Validate environment variables for database connections
- Use SSL connections in production
- Limit database user permissions
- Store secrets in environment variables
- Never commit sensitive data to version control
- Use different secrets for different environments
- Rotate secrets regularly
This guide should help you get started with development on the fastapi-base project. For additional questions, please refer to the Architecture Overview, Contributing Guidelines, or Troubleshooting Guide for common issues and solutions.