FastAPI setup with uv
Manage project dependencies with uv package manager
Install mise
Install Python and uv using mise
[mise + python][https://mise.jdx.dev/lang/python.html]
mise use -g python@latest
mise use -g uv@latest
Create project
uv init fastapi-uv-demo
Install dependencies
uv add "fastapi[standard]" sqlalchemy "psycopg[binary]" alembic pydantic-settings
Activate virtual enviroment
source .venv/bin/activate
Create project structure
mkdir -p app/{core,models,routers,schemas,services}
touch app/{core,models,routers,schemas,services}/__init__.py
Move main.py to app folder
mv main.py app
Init alembic
alembic init migrations
Manage enviroment variables
Create .env.example and copy to .env file
# PostgreSQL Configuration
POSTGRES_USER=postgres_user
POSTGRES_PASSWORD=my_secure_password123
POSTGRES_DB=fastapi_db
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
cp .env.example .env
Change .env credentials
Add .env to gitignore
# Env Files
.env
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
# Virtual environments
.venv
Final structure
.
├── app
│ ├── alembic.ini
│ ├── core
│ │ ├── config.py
│ │ └── __init__.py
│ ├── main.py
│ ├── migrations
│ │ ├── env.py
│ │ ├── README
│ │ ├── script.py.mako
│ │ └── versions
│ ├── models
│ │ └── __init__.py
│ ├── routers
│ │ └── __init__.py
│ ├── schemas
│ │ └── __init__.py
│ └── services
│ └── __init__.py
├── .env
├── .env.example
├── .gitignore
├── pyproject.toml
├── .python-version
├── README.md
└── uv.lock
App structure breakdown
core
It contains config.py and database.py files.
config.py: type-safe configuration management using Pydantic Settings.
from dotenv import find_dotenv
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
# Postgres Settings
POSTGRES_USER: str
POSTGRES_PASSWORD: str
POSTGRES_DB: str
POSTGRES_HOST: str
POSTGRES_PORT: int
@property
def DATABASE_URL(self) -> str:
return f"postgresql+psycopg://{self.POSTGRES_USER}:{self.POSTGRES_PASSWORD}@{self.POSTGRES_HOST}:{self.POSTGRES_PORT}/{self.POSTGRES_DB}"
model_config = SettingsConfigDict(env_file=find_dotenv(), env_file_encoding="utf-8")
settings = Settings()
database.py: database connection engine setup using SQLAlchemy.
from sqlalchemy import create_engine
from sqlalchemy.orm import DeclarativeBase, sessionmaker
from .config import settings
engine = create_engine(settings.DATABASE_URL)
SessionLocal = sessionmaker(bind=engine, autoflush=False)
class Base(DeclarativeBase):
pass
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
models
It contains your database schema.
user.py: User model class that inherits from Base.
from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column
from core.database import Base
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
username: Mapped[str] = mapped_column(String(50), unique=True, nullable=False)
email: Mapped[str] = mapped_column(String(100), unique=True, nullable=False)
password_hash: Mapped[str] = mapped_column(String(255), nullable=False)
schemas
This folder defines the inputs and outputs of your API endpoints using Pydantic. It validates data coming into your application from the user, and structures data going out to the user.
User register request example:
from typing import Annotated, Self
from pydantic import BaseModel, EmailStr, Field, model_validator
PasswordStr = Annotated[
str,
Field(
min_length=8,
max_length=100,
pattern=r"^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[@$!%*?&])[A-Za-z0-9@$!%*?&]+$",
),
]
class UserRegisterRequest(BaseModel):
username: Annotated[str, Field(min_length=3, max_length=20)]
email: EmailStr
password: PasswordStr
confirm_password: str
@model_validator(mode="after")
def check_passwords_match(self) -> Self:
if self.password != self.confirm_password:
raise ValueError("Passwords do not match")
return self
routers
This folder defines your HTTP routes (@router.get, @router.post).
auth.py: Basic register example.