Aptiwise
Aptiwise
Aptiwise DocumentationForm Layout Feature - Implementation CompleteForm Layout Feature Implementation Guide
Getting Started
User Guide
Development GuideProduction DeploymentProject StructureTesting Guide
API Development
Authentication & Security
Background Tasks
Caching
Configuration
Database Layer
Rate Limiting
Workflow Types & Patterns
User Guide

Development Guide

Development Guide

This guide covers everything you need to know about extending, customizing, and developing with the FastAPI boilerplate.

Extending the Boilerplate

Creating Custom Middleware

Create middleware in src/app/middleware/:

from fastapi import Request, Response
from starlette.middleware.base import BaseHTTPMiddleware


class CustomHeaderMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next):
        # Pre-processing
        start_time = time.time()

        # Process request
        response = await call_next(request)

        # Post-processing
        process_time = time.time() - start_time
        response.headers["X-Process-Time"] = str(process_time)

        return response

Register in src/app/main.py:

from .middleware.custom_header_middleware import CustomHeaderMiddleware

app.add_middleware(CustomHeaderMiddleware)

Testing

Test Configuration

The boilerplate uses pytest for testing. Test configuration is in pytest.ini and test dependencies in pyproject.toml.

Writing Tests

Model Tests

# tests/test_models.py
import pytest
from src.app.models.user import User


@pytest_asyncio.fixture
async def test_user(async_session):
    user = User(name="Test User", username="testuser", email="test@example.com", hashed_password="hashed_password")
    async_session.add(user)
    await async_session.commit()
    await async_session.refresh(user)
    return user


async def test_user_creation(test_user):
    assert test_user.name == "Test User"
    assert test_user.username == "testuser"
    assert test_user.email == "test@example.com"

API Endpoint Tests

# tests/test_api.py
import pytest
from httpx import AsyncClient


async def test_create_user(async_client: AsyncClient):
    user_data = {"name": "New User", "username": "newuser", "email": "new@example.com", "password": "SecurePass123!"}

    response = await async_client.post("/services/v1/users", json=user_data)
    assert response.status_code == 201

    data = response.json()
    assert data["name"] == "New User"
    assert data["username"] == "newuser"
    assert "hashed_password" not in data  # Ensure password not exposed


async def test_read_users(async_client: AsyncClient):
    response = await async_client.get("/services/v1/users")
    assert response.status_code == 200

    data = response.json()
    assert "data" in data
    assert "total_count" in data

CRUD Tests

# tests/test_crud.py
import pytest
from src.app.crud.crud_users import crud_users
from src.app.schemas.user import UserCreate


async def test_crud_create_user(async_session):
    user_data = UserCreate(name="CRUD User", username="cruduser", email="crud@example.com", password="password123")

    user = await crud_users.create(db=async_session, object=user_data)
    assert user["name"] == "CRUD User"
    assert user["username"] == "cruduser"


async def test_crud_get_user(async_session, test_user):
    retrieved_user = await crud_users.get(db=async_session, id=test_user.id)
    assert retrieved_user["name"] == test_user.name

Running Tests

# Run all tests
uv run pytest

# Run with coverage
uv run pytest --cov=src

# Run specific test file
uv run pytest tests/test_api.py

# Run with verbose output
uv run pytest -v

# Run tests matching pattern
uv run pytest -k "test_user"

Customization

Environment-Specific Configuration

Create environment-specific settings:

# src/app/core/config.py
class LocalSettings(Settings):
    ENVIRONMENT: str = "local"
    DEBUG: bool = True


class ProductionSettings(Settings):
    ENVIRONMENT: str = "production"
    DEBUG: bool = False
    # Production-specific settings


def get_settings():
    env = os.getenv("ENVIRONMENT", "local")
    if env == "production":
        return ProductionSettings()
    return LocalSettings()


settings = get_settings()

Custom Logging

Configure logging in src/app/core/config.py:

import logging
from pythonjsonlogger import jsonlogger


def setup_logging():
    # JSON logging for production
    if settings.ENVIRONMENT == "production":
        logHandler = logging.StreamHandler()
        formatter = jsonlogger.JsonFormatter()
        logHandler.setFormatter(formatter)
        logger = logging.getLogger()
        logger.addHandler(logHandler)
        logger.setLevel(logging.INFO)
    else:
        # Simple logging for development
        logging.basicConfig(level=logging.DEBUG, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")

Opting Out of Services

Disabling Redis Caching

  1. Remove cache decorators from endpoints
  2. Update dependencies in src/app/core/config.py:
class Settings(BaseSettings):
    # Comment out or remove Redis cache settings
    # REDIS_CACHE_HOST: str = "localhost"
    # REDIS_CACHE_PORT: int = 6379
    pass
  1. Remove Redis cache imports and usage

Disabling Background Tasks (ARQ)

  1. Remove ARQ from pyproject.toml dependencies
  2. Remove worker configuration from docker-compose.yml
  3. Delete src/app/core/worker/ directory
  4. Remove task-related endpoints

Disabling Rate Limiting

  1. Remove rate limiting dependencies from endpoints:
# Remove this dependency
dependencies = [Depends(rate_limiter_dependency)]
  1. Remove rate limiting models and schemas
  2. Update database migrations to remove rate limit tables

Disabling Authentication

  1. Remove JWT dependencies from protected endpoints
  2. Remove user-related models and endpoints
  3. Update database to remove user tables
  4. Remove authentication middleware

Minimal FastAPI Setup

For a minimal setup with just basic FastAPI:

# src/app/main.py (minimal version)
from fastapi import FastAPI

app = FastAPI(title="Minimal API", description="Basic FastAPI application", version="1.0.0")


@app.get("/")
async def root():
    return {"message": "Hello World"}


@app.get("/health")
async def health_check():
    return {"status": "healthy"}

Best Practices

Code Organization

  • Keep models, schemas, and CRUD operations in separate files
  • Use consistent naming conventions across the application
  • Group related functionality in modules
  • Follow FastAPI and Pydantic best practices

Database Operations

  • Always use transactions for multi-step operations
  • Implement soft deletes for important data
  • Use database constraints for data integrity
  • Index frequently queried columns

API Design

  • Use consistent response formats
  • Implement proper error handling
  • Version your APIs from the start
  • Document all endpoints with proper schemas

Security

  • Never expose sensitive data in API responses
  • Use proper authentication and authorization
  • Validate all input data
  • Implement rate limiting for public endpoints
  • Use HTTPS in production

Performance

  • Use async/await consistently
  • Implement caching for expensive operations
  • Use database connection pooling
  • Monitor and optimize slow queries
  • Use pagination for large datasets

Troubleshooting

Common Issues

Import Errors: Ensure all new models are imported in __init__.py files

Migration Failures: Check model definitions and relationships before generating migrations

Test Failures: Verify test database configuration and isolation

Performance Issues: Check for N+1 queries and missing database indexes

Authentication Problems: Verify JWT configuration and token expiration settings

Debugging Tips

  • Use FastAPI's automatic interactive docs at /docs
  • Enable SQL query logging in development
  • Use proper logging throughout the application
  • Test endpoints with realistic data volumes
  • Monitor database performance with query analysis

User Guide

Previous Page

Production Deployment

Next Page

On this page

Development GuideExtending the BoilerplateCreating Custom MiddlewareTestingTest ConfigurationWriting TestsModel TestsAPI Endpoint TestsCRUD TestsRunning TestsCustomizationEnvironment-Specific ConfigurationCustom LoggingOpting Out of ServicesDisabling Redis CachingDisabling Background Tasks (ARQ)Disabling Rate LimitingDisabling AuthenticationMinimal FastAPI SetupBest PracticesCode OrganizationDatabase OperationsAPI DesignSecurityPerformanceTroubleshootingCommon IssuesDebugging Tips