The backend for a personal tracker agent, built with FastAPI, SQLAlchemy, Pydantic and PostgreSQL, laid out according to Clean Architecture.
The website is a separate repository. This one exposes the HTTP API only.
Read
docs/architecture.mdbefore writing code. It defines the layers, the dependency rule and the three model types. The rules there are enforced automatically byimport-linterin CI, not just by convention.
| Concern | Choice |
|---|---|
| Package manager | uv |
| Lint + format | Ruff |
| Type checker | ty (preview) |
| Web framework | FastAPI |
| ORM / migrations | SQLAlchemy 2 (async) + Alembic |
| Validation | Pydantic v2 / pydantic-settings |
| Database | PostgreSQL 17 |
| Cache | Redis 8 |
| Tests | pytest + pytest-asyncio + httpx |
| Architecture rules | import-linter |
cp .env.example .env && docker compose up --build- API: http://localhost:8000
- OpenAPI docs: http://localhost:8000/docs
- Health: http://localhost:8000/health
Migrations run automatically on container start.
uv syncThen, with PostgreSQL and Redis running (docker compose up -d postgres redis):
uv run alembic upgrade head
uv run fastapi dev src/app/drivers/rest/main.pymake help| Command | What it does |
|---|---|
make install |
uv sync + install the pre-commit hooks |
make lint |
Ruff check + format check, ty, and the architecture contracts |
make format |
Ruff autofix + format |
make test |
The whole suite |
make test-unit |
Unit tests only — no database needed |
make migration m="add x" |
Autogenerate an Alembic revision |
make upgrade |
Apply migrations |
make up / down |
Start / stop the Docker stack |
The habits slice is the reference implementation. To add one, work outwards:
src/app/domain/— entity and value objects, with their invariants.src/app/ports/— the interface the use case needs (if a new one is needed).src/app/use_cases/dto/— the DTOs in and out.src/app/use_cases/<feature>/— one class per use case, one__call__.src/app/adapters/— ORM model, mapper, repository implementation.src/app/drivers/rest/— request/response models, router, wiring independencies.py.- Tests: unit against the in-memory adapter, integration against PostgreSQL and the ASGI app.
Run make lint — if you got a layer wrong, the architecture contracts will say so.
src/app/
├── domain/ entities + value objects (no dependencies at all)
├── ports/ abstract interfaces (depends on: domain)
├── use_cases/ application logic + DTOs (depends on: ports, domain)
├── adapters/ PostgreSQL, Redis, clock, ORM (depends on: use_cases, ports, domain)
├── drivers/ FastAPI: routers, schemas, DI (depends on: everything)
└── config/ settings (only adapters + drivers may import it)
- ty is in preview. It is wired into
make lint, pre-commit and CI. If it ever blocks you,make lintstill runs Ruff and the architecture contracts; see ADR-0004. - Commit messages follow Conventional Commits.