| name | fastapi-setup |
| description | Initialize FastAPI backend projects with UV package manager, configure project structure, install dependencies, and set up basic FastAPI application. Use when setting up a new FastAPI backend or initializing the backend directory for Phase 2. |
| allowed-tools | Bash, Write, Read, Glob |
FastAPI Project Setup
Quick reference for initializing FastAPI backend projects with modern tooling (UV, SQLModel, pytest).
Quick Start
1. Initialize Backend with UV
cd backend
# Initialize Python project with UV
uv init --name backend --python 3.13
# Create project structure
mkdir -p src/models src/routers src/services src/middleware src/schemas src/utils tests alembic
# Create __init__.py files
touch src/__init__.py
touch src/models/__init__.py
touch src/routers/__init__.py
touch src/services/__init__.py
touch src/middleware/__init__.py
touch src/schemas/__init__.py
touch src/utils/__init__.py
touch tests/__init__.py
2. Install Dependencies
# Core dependencies
uv add fastapi[all]
uv add sqlmodel
uv add psycopg2-binary
uv add python-jose[cryptography]
uv add passlib[bcrypt]
uv add python-dotenv
uv add alembic
# Development dependencies
uv add --dev pytest
uv add --dev pytest-cov
uv add --dev httpx
uv add --dev pytest-asyncio
3. Create Basic FastAPI App
Create src/main.py:
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
import os
from dotenv import load_dotenv
load_dotenv()
app = FastAPI(
title="Todo API",
description="RESTful API for Todo Web Application - Phase 2",
version="1.0.0"
)
# CORS configuration
origins = [
os.getenv("FRONTEND_URL", "http://localhost:3000"),
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/")
async def root():
return {"message": "Todo API is running", "version": "1.0.0"}
@app.get("/health")
async def health_check():
return {"status": "healthy"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
4. Create Configuration
Create src/config.py:
from pydantic_settings import BaseSettings
from functools import lru_cache
class Settings(BaseSettings):
# Database
DATABASE_URL: str
# Authentication
BETTER_AUTH_SECRET: str
JWT_ALGORITHM: str = "HS256"
ACCESS_TOKEN_EXPIRE_DAYS: int = 7
# CORS
FRONTEND_URL: str = "http://localhost:3000"
# Environment
ENVIRONMENT: str = "development"
class Config:
env_file = ".env"
case_sensitive = True
@lru_cache()
def get_settings():
return Settings()
settings = get_settings()
5. Create Environment Variables Template
Create .env.example:
# Database
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
# Authentication
BETTER_AUTH_SECRET=your-secret-key-here-min-32-characters
# CORS
FRONTEND_URL=http://localhost:3000
# Environment
ENVIRONMENT=development
Create actual .env file:
cp .env.example .env
# Edit .env with actual values
6. Set Up Database Connection
Create src/database.py:
from sqlmodel import create_engine, Session, SQLModel
from sqlalchemy.pool import NullPool
from src.config import settings
engine = create_engine(
settings.DATABASE_URL,
echo=settings.ENVIRONMENT == "development",
poolclass=NullPool, # Let Neon handle pooling
connect_args={
"connect_timeout": 10,
"options": "-c timezone=utc"
}
)
def create_db_and_tables():
"""Create all database tables"""
SQLModel.metadata.create_all(engine)
def get_session():
"""FastAPI dependency for database sessions"""
with Session(engine) as session:
yield session
7. Initialize Alembic for Migrations
# Initialize Alembic
alembic init alembic
# Edit alembic.ini - comment out the sqlalchemy.url line
# It will be set from environment variable
# Edit alembic/env.py
Update alembic/env.py:
from logging.config import fileConfig
from sqlmodel import SQLModel
from sqlalchemy import engine_from_config, pool
from alembic import context
import os
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
# Import all models so Alembic can detect them
from src.models.task import Task # Import as you create models
# Alembic Config object
config = context.config
# Set DATABASE_URL from environment
config.set_main_option("sqlalchemy.url", os.getenv("DATABASE_URL"))
# Interpret the config file for Python logging
if config.config_file_name is not None:
fileConfig(config.config_file_name)
# Set target metadata for autogenerate
target_metadata = SQLModel.metadata
# ... rest of env.py (keep default functions)
8. Create Basic Test Setup
Create tests/conftest.py:
import pytest
from fastapi.testclient import TestClient
from sqlmodel import Session, create_engine, SQLModel
from sqlmodel.pool import StaticPool
from src.main import app
from src.database import get_session
@pytest.fixture(name="session")
def session_fixture():
engine = create_engine(
"sqlite:///:memory:",
connect_args={"check_same_thread": False},
poolclass=StaticPool,
)
SQLModel.metadata.create_all(engine)
with Session(engine) as session:
yield session
@pytest.fixture(name="client")
def client_fixture(session: Session):
def get_session_override():
return session
app.dependency_overrides[get_session] = get_session_override
client = TestClient(app)
yield client
app.dependency_overrides.clear()
Create tests/test_main.py:
def test_root(client):
response = client.get("/")
assert response.status_code == 200
assert "message" in response.json()
def test_health_check(client):
response = client.get("/health")
assert response.status_code == 200
assert response.json()["status"] == "healthy"
9. Create pyproject.toml
Create or update pyproject.toml:
[project]
name = "backend"
version = "1.0.0"
description = "Todo API Backend - Phase 2"
requires-python = ">=3.13"
dependencies = [
"fastapi[all]>=0.115.0",
"sqlmodel>=0.0.24",
"psycopg2-binary>=2.9.9",
"python-jose[cryptography]>=3.3.0",
"passlib[bcrypt]>=1.7.4",
"python-dotenv>=1.0.0",
"alembic>=1.13.0",
]
[project.optional-dependencies]
dev = [
"pytest>=8.0.0",
"pytest-cov>=4.1.0",
"httpx>=0.26.0",
"pytest-asyncio>=0.23.0",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = "-v --cov=src --cov-report=term-missing"
10. Test the Setup
# Run the FastAPI server
cd backend
uv run uvicorn src.main:app --reload --port 8000
# In another terminal, run tests
uv run pytest
# Check code coverage
uv run pytest --cov=src --cov-report=html
Verification Checklist
After setup, verify:
-
uv run uvicorn src.main:app --reloadstarts successfully - http://localhost:8000 returns JSON response
- http://localhost:8000/docs shows Swagger UI
- http://localhost:8000/health returns healthy status
-
uv run pytestruns and passes -
.envfile exists and has DATABASE_URL - All init.py files created
- Alembic initialized successfully
Next Steps
After basic setup:
- Create database models in
src/models/ - Create API routers in
src/routers/ - Set up JWT authentication middleware
- Create first Alembic migration
- Write comprehensive tests
Troubleshooting
UV not found:
curl -LsSf https://astral.sh/uv/install.sh | sh
Import errors:
- Ensure all init.py files exist
- Check PYTHONPATH includes src directory
- Use
uv run pythoninstead of justpython
Database connection errors:
- Verify DATABASE_URL in .env
- Check Neon database is running
- Test connection with psql
References
- FastAPI: https://fastapi.tiangolo.com/
- UV: https://docs.astral.sh/uv/
- SQLModel: https://sqlmodel.tiangolo.com/
- Alembic: https://alembic.sqlalchemy.org/