fastapi-setup
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.
SKILL.md
Full skill instructions
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/
