Monorepo for a budgeting & transaction categorization app:
- Backend: FastAPI + SQLModel + Alembic (PostgreSQL in production, SQLite dev fallback)
- Frontend: Next.js 15 (App Router) + TypeScript + Tailwind CSS v4
- Data: CSV ingestion & categorisation, filters, summaries, category editing
- Start stack:
docker compose up --build(or run backend +pnpm devfor frontend). - Go to http://localhost:3000.
- (If upload page present) Upload / import your bank CSV; transactions appear in the table.
- Open Dashboard → pick a date range (shortcuts available) to view spending by category.
- Click a category card to drill into filtered transactions.
- In Transactions view: refine with text & date filters, inline edit or bulk-assign categories, then Reset to clear.
- Return to Dashboard—your last date range persists via URL params for easy sharing.
For more detail see Section 5 (Using the Application).
- Project Structure
- Quick Start (Docker)
- Local Development
- Environment Variables
- Using the Application
- API Usage
- Database & Migrations
- Lint / Format / Quality
- Versioning & Releases
- Conventional Commits
- Make / Scripts
- Troubleshooting
- Roadmap
.
├── backend/ # FastAPI app, models, services, migrations
├── frontend/ # Next.js 15 app router UI
├── docker-compose.yml # Postgres + backend + frontend
├── Makefile # Helper targets (uv & compose)
└── .github/workflows # CI pipelines
Backend highlights: app/models, app/daos, app/services, app/api/routes, alembic/versions.
Frontend highlights: src/app routes, src/components, src/services/api.ts.
docker compose up --buildServices:
- API: http://localhost:8000 (docs at /docs)
- UI: http://localhost:3000
Tear down:
docker compose down -vmake uv-set # create .venv & install deps via uv
make run-backend # uvicorn reload server on :8000Or manual steps: create venv, uv pip install -r requirements.txt, run uvicorn app.main:app --reload.
cd frontend
pnpm install
pnpm dev # http://localhost:3000Ensure NEXT_PUBLIC_API_BASE_URL matches backend (default http://localhost:8000).
Backend (backend/.env):
DATABASE_URL(ordatabase_url) – SQLAlchemy URL. Docker sets Postgres host.DB_ECHO– SQL logging (dev)ALLOWED_ORIGINS– CORS listOPENAI_API_KEY/ embedding model vars (optional future classification)SCHEDULER_ENABLED,SCHEDULER_TIMEZONE
Frontend (frontend/.env.local):
NEXT_PUBLIC_API_BASE_URL– base URL for API
- Dashboard: Pick date range (shortcuts: last 30 days, last six months). See category totals.
- Click a category → Transactions view filtered by that category + date range.
- Transactions Table: text filters, category filter, date range, has/has not category, bulk & inline category editing, Reset clears state + URL params.
- Single transaction page: inspect details & (future) suggestions.
- Categories page (planned enhancements) for management.
URL query keeps date range for shareable links.
Key endpoints:
GET /transactionswith filters:counterparty_name_ilike,transaction_type_ilike,notes_ilike,booking_date_gte,booking_date_lte,category_id,has_category.POST /transactions/{id}/category{ "category": "Groceries" }POST /transactions/bulk/category{ "ids": [..], "category": "Fuel" }GET /categories/summarydate-range summaryGET /version
Example:
curl 'http://localhost:8000/transactions?limit=20&booking_date_gte=2025-01-01&booking_date_lte=2025-01-31'Generate & apply:
cd backend
alembic revision --autogenerate -m "add field"
alembic upgrade headInside containers:
docker compose exec backend alembic upgrade headSQLite for quick dev; Postgres for production features (advisory locks, scheduler persistence).
Backend: Ruff (format, import order). Frontend: ESLint 9 flat config (import ordering, 4-space indent, hooks). Conventional Commits enforced in CI.
Semantic version bumps via commit messages; workflow updates backend pyproject.toml + frontend package.json and tags vX.Y.Z. Endpoints: /version and /api/version.
Format type(scope)!: subject. Types: feat, fix, perf, refactor, chore, docs, test, build, ci, style. Use ! or BREAKING CHANGE: footer for majors.
Examples:
feat(frontend): add date range quick shortcuts
fix(backend): handle empty csv row gracefully
feat!: switch primary key type to ULID
BREAKING CHANGE: existing references to integer IDs must be migrated.
Make: uv-set, run-backend, alembic-rev, alembic-up, compose-up, compose-down.
Frontend: pnpm dev|build|start|lint.
| Issue | Cause | Resolution |
|---|---|---|
| 404 from UI | Wrong API base | Set NEXT_PUBLIC_API_BASE_URL |
| DB connect fail | Postgres not up | Start Docker or adjust DATABASE_URL |
| No migrations generated | Models not imported | Ensure model imports in Alembic env |
| ESLint plugin missing | Dependencies stale | pnpm install in frontend |
| Slow dev responses | DB echo + reload overhead | Disable DB_ECHO, use Postgres |
- Category suggestion ML improvements
- Budgets & variance tracking
- Multi-user auth / roles
- Export / import enhancements
- Tagging (multi-label) & rules engine
- E2E tests (Playwright), API tests (Pytest)
Happy budgeting! 🎯