A robust, production-ready FastAPI backend with structured tooling, OpenTelemetry, Celery, and more.
git clone https://github.com/ixdlabs/fastapi-templateRequires Python 3.14+ and uv. uv manages the environment - no venv activation needed.
This project uses a uv.lock file for consistent dependency installation.
uv syncCopy .env.example to .env (create one if missing) and adjust values as needed.
This project uses pre-commit hooks with ruff and pyright strict mode.
# Install pre-commit hooks
uv run pre-commit install
# Run formatting and type checks
uv run pre-commit --all-filesRun the test suite (pytest):
uv run pytestSQLite is the default for development and will create sqlite.db automatically.
No additional setup is required to start the API.
For development, PostgreSQL is strongly recommended. Using a different database engine may lead to issues when generating migrations.
To switch to PostgreSQL, set:
DATABASE_URL=postgresql+asyncpg://user:password@localhost:5432/dbnameRun migrations after changing models or switching databases:
# Apply migrations
uv run alembic upgrade head
# Create new migration
uv run alembic revision --autogenerate -m "describe change"
# Downgrade one migration
uv run alembix downgrade -1This application uses OpenTelemetry (OTel) to export traces, metrics, and logs via OTLP. SigNoz is the recommended backend, but any OTLP-compatible collector will work.
Add the following variables to .env file:
# Set false (default) to disable otel
OTEL_ENABLED=true
# Resource settings to uniquely identify the service
OTEL_RESOURCE_SERVICE_NAME=backend
OTEL_RESOURCE_ENVIRONMENT=development
# Only OTEL_EXPORTER_OTLP_ENDPOINT is required for self-hosted SigNoz
OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.<region>.signoz.cloud:443"
OTEL_EXPORTER_OTLP_INSECURE=false
OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=<your-ingestion-key>"Do not use autoreload when running with OpenTelemetry instrumentation.
Start the application using:
uv run uvicorn app.web_app:appTo discover recommended instrumentations for the environment, run:
uv run opentelemetry-bootstrapThis command lists optional packages (e.g., FastAPI, SQLAlchemy, HTTP clients) that can be instrumented. Select only the ones that is used directly in the application.
Install only the instrumentations that are needed:
uv add --group=opentelemetry <package>
# Example:
# uv add --group=opentelemetry opentelemetry-instrumentation-fastapi
# uv add --group=opentelemetry opentelemetry-instrumentation-sqlalchemyThe instrumentations should be configured inside the app/core/otel.py.
Once configured, traces, metrics, and logs are automatically exported via OTLP and data appears in SigNoz (or your chosen backend).
Run after applying migrations:
uv run fastapi dev app/web_app.pyDocs available at: http://127.0.0.1:8000/api/docs
- FastAPI for the web framework and dependency injection.
- SQLAlchemy async with Alembic for migrations.
- Pydantic for request/response models and settings management.
- PyJWT for JWT authentication.
- Celery for background tasks.
- pytest for unit testing.
Celery is configured for async task execution.
Run tasks without a worker:
CELERY_TASK_ALWAYS_EAGER=trueDefault queue backend is SQLite. Run following commands to start the worker and the beat scheduler.
# Worker - do the actual work
uv run celery -A app.worker_app worker
# Beat Scheduler - schedule periodic tasks
uv run celery -A app.worker_app beatBuild & run the production image:
docker build -t fastapi-template .
docker run --rm -p 8000:8000 --env-file .env fastapi-templateFor persistent storage, use a volume or PostgreSQL via DATABASE_URL.
You can apply migrations in the container before first run if you use an external DB:
docker run --rm --env-file .env fastapi-template uv run alembic upgrade headapp/
ββ core/ # Global configuration & shared infrastructure
β ββ emails/ # Email templates & base components
β ββ tests/ # Core-level tests
β ββ <module_1>.py
β
ββ features/ # Feature-based modules (vertical slices)
β ββ <domain_1>/ # Example feature/domain
β β ββ models/ # Database models
β β β ββ tests/ # Model tests
β β β ββ <model_1>.py
β β β
β β ββ services/ # Business logic & API handlers
β β β ββ common/ # Endpoints shared across user types
β β β ββ <user_type>/ # User-typeβspecific endpoints
β β β ββ tasks/ # Async/worker services
β β β ββ tests/ # Task tests
β β β ββ <do_action>.py # **Single-responsibility task
β β β
β β ββ api.py # Feature-level API router
β β ββ tasks.py # Feature-level Celery task registry
β β
β ββ api.py # Aggregate feature routers
β ββ tasks.py # Aggregate feature tasks
β ββ models.py # Aggregate feature models
β
ββ fixtures/ # Test factories & fixtures
β ββ <model_1_factory>.py
β
ββ migrations/ # Alembic migration root
β ββ versions/ # Migration files
β β ββ <datetime>_<id>_<slug>.py
β ββ env.py # Alembic runtime configuration
β ββ script.mako # Migration template
β
ββ conftest.py # Pytest global configuration
ββ fastapi.py # FastAPI app factory / wiring
ββ web_app.py # Web application entry point
ββ celery.py # Celery app factory / wiring
ββ worker_app.py # Worker entry point
β
ββ pyproject.toml # Project metadata & dependencies
ββ uv.lock # Dependency lockfile
The single responsibility task should have below structure.
# ... imports
logger = logging.getLogger(__name__)
router = APIRouter() # ... for API endpoints
registry = TaskRegistry() # ... for worker tasks
# Input/Output
# -----------------------------------------------------------------------------
class DoActionInput(BaseModel):
...
class DoActionOutput(BaseModel):
...
# Exceptions
# -----------------------------------------------------------------------------
class Example1Exception(ServiceException):
status_code = status.HTTP_404_NOT_FOUND
type = "<domain>/<user-type>/<do-action>/example-exception-1"
detail = "Example exception 1 message"
# Action
# -----------------------------------------------------------------------------
# ... for API endpoints
@raises(Example1Exception)
@router.post("/do-action")
async def do_action(form: DoActionInput, dep1: Example1Dep, ...) -> DoActionOutput:
"""
Documentation for the action with any special notes.
"""
# ... for worker tasks
@registry.background_task("do_action")
async def do_action(task_input: DoActionInput, dep1: Example1WorkerDep, ...) -> DoActionOutput:
"""
Documentation for the action with any special notes.
"""
DoActionTaskDep = Annotated[BackgroundTask, Depends(do_action)]
# ... for service methods
async def do_action(form: DoActionInput, dep1: Example1Dep, ...) -> DoActionOutput:
"""
Documentation for the action with any special notes.
"""The single responsibility task denoting a worker task should have below structure.
{
"python.testing.pytestArgs": ["app"],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
"editor.rulers": [120]
}