Last Updated: February 7, 2026 Architecture: Static Frontend + REST API Backend (Decoupled)
Spends Tracker uses a modern decoupled architecture with a static frontend and REST API backend. This design provides:
- ✅ Independent deployment - Frontend and backend can be deployed separately
- ✅ Scalability - Each layer can scale independently
- ✅ Developer experience - Frontend and backend teams can work independently
- ✅ Cost efficiency - Frontend can be hosted for free on CDN
- ✅ Performance - Static files served from edge locations
┌─────────────────────────────────────────────────────────────┐
│ CLIENT │
│ (Web Browser) │
└────────────────────┬────────────────────────────────────────┘
│
│ HTTPS
│
┌────────────┴─────────────┐
│ │
│ Static HTML/CSS/JS │ API Requests (/api/*)
│ (from CDN/Netlify) │ (JSON)
│ │
▼ ▼
┌──────────────────┐ ┌────────────────────┐
│ FRONTEND │ │ BACKEND │
│ │ │ │
│ Static Files │ │ FastAPI Server │
│ - HTML Pages │ │ - REST API │
│ - CSS (SCSS) │ │ - Business Logic │
│ - JavaScript │ │ - Database │
│ - Alpine.js │ │ │
│ - Chart.js │ │ Port: 8000 │
│ │ │ │
│ Port: 3000 (dev)│ └─────────┬──────────┘
│ CDN (prod) │ │
└──────────────────┘ │
▼
┌────────────────┐
│ DATABASE │
│ │
│ SQLite (dev) │
│ PostgreSQL │
│ (production) │
└────────────────┘
spends-tracker/
├── src-modern/ # Frontend source code
│ ├── index.html # Dashboard page
│ ├── inventory.html # Inventory page
│ ├── settings.html # Settings page
│ ├── manifest.json # PWA manifest
│ ├── scripts/
│ │ ├── main.js # Entry point
│ │ ├── components/ # Page components
│ │ │ ├── dashboard.js
│ │ │ ├── inventory.js
│ │ │ ├── settings.js
│ │ │ └── sidebar.js
│ │ └── utils/ # Utilities
│ │ ├── theme-manager.js
│ │ ├── notifications.js
│ │ └── icon-manager.js
│ └── styles/scss/ # SCSS styles (24 files)
│ ├── main.scss
│ ├── abstracts/ # Variables, mixins
│ ├── components/ # Component styles
│ ├── layout/ # Layout styles
│ ├── pages/ # Page-specific styles
│ └── themes/ # Dark/light themes
│
├── dist-modern/ # Frontend build output
│ ├── index.html # Built HTML
│ ├── inventory.html
│ ├── settings.html
│ └── assets/ # Bundled JS/CSS with hashes
│ ├── main-[hash].js
│ ├── main-[hash].css
│ └── vendor-[hash].js
│
├── backend/ # Backend API
│ ├── app/
│ │ ├── main.py # FastAPI app (serves API + static files)
│ │ ├── config.py # Configuration
│ │ ├── database.py # Database connection
│ │ ├── models/ # SQLAlchemy models
│ │ │ ├── purchase.py
│ │ │ ├── warranty.py
│ │ │ ├── retailer.py
│ │ │ ├── brand.py
│ │ │ └── setting.py # User settings (DB-persisted)
│ │ ├── schemas/ # Pydantic schemas
│ │ │ ├── purchase.py
│ │ │ ├── warranty.py
│ │ │ ├── analytics.py
│ │ │ └── common.py
│ │ ├── routes/ # API endpoints
│ │ │ ├── purchases.py # /api/purchases
│ │ │ ├── warranties.py # /api/warranties
│ │ │ ├── retailers.py # /api/retailers
│ │ │ ├── brands.py # /api/brands
│ │ │ ├── analytics.py # /api/analytics
│ │ │ ├── exports.py # /api/export
│ │ │ ├── imports.py # /api/import
│ │ │ └── settings.py # /api/settings
│ │ ├── services/ # Business logic
│ │ │ ├── purchase_service.py
│ │ │ ├── warranty_service.py
│ │ │ ├── analytics_service.py
│ │ │ ├── retailer_service.py
│ │ │ └── brand_service.py
│ │ └── utils/ # Utilities
│ │ └── import_export.py
│ ├── tests/ # Test suite
│ │ ├── conftest.py
│ │ ├── test_purchases.py
│ │ └── test_analytics.py
│ ├── migrations/ # Alembic migrations
│ ├── scripts/
│ │ └── seed_data.py # Sample data
│ ├── requirements.txt # Python dependencies
│ ├── alembic.ini # Migration config
│ ├── pytest.ini # Test config
│ ├── Dockerfile # Container image
│ └── docker-compose.yml # Local PostgreSQL
│
├── public-assets/ # Static assets (not built)
│ └── assets/
│ ├── images/ # Images, logos
│ └── icons/ # Favicons, PWA icons
│
├── package.json # Frontend dependencies
├── vite.config.js # Vite configuration
├── README.md # Project overview
├── DEVELOPMENT.md # Development plan
└── ARCHITECTURE.md # This file
Developer runs TWO servers:
Terminal 1:
$ npm run dev
→ Vite dev server on http://localhost:3000
→ Hot Module Reload (HMR) enabled
→ Proxies /api/* requests to backend
Terminal 2:
$ cd backend && python run_server.py
→ FastAPI server on http://localhost:8000
→ Serves API endpoints only
→ Returns JSON responses
Request Flow:
Browser → http://localhost:3000/
→ Vite serves src-modern/index.html (with HMR)
Browser → http://localhost:3000/api/purchases
→ Vite proxies to http://localhost:8000/api/purchases
→ FastAPI returns JSON
→ Alpine.js renders data
$ npm run build
→ Builds frontend to dist-modern/
$ cd backend && python run_server.py
→ FastAPI serves:
- Static files from dist-modern/ at /
- API endpoints at /api/*
Request Flow:
Browser → http://localhost:8000/
→ FastAPI serves dist-modern/index.html (static file)
Browser → http://localhost:8000/api/purchases
→ FastAPI returns JSON
→ Alpine.js renders data
Frontend on CDN:
$ npm run build
$ deploy dist-modern/ to Netlify/Vercel/S3
→ https://spends-tracker.netlify.app/
Backend on Server:
$ deploy backend/ to Railway/Render/Heroku
→ https://api.spends-tracker.com/
Frontend Config:
// In production build:
const API_URL = 'https://api.spends-tracker.com';Request Flow:
Browser → https://spends-tracker.netlify.app/
→ CDN serves static HTML/CSS/JS
Browser → https://api.spends-tracker.com/api/purchases
→ Backend returns JSON
→ Alpine.js renders data
| Component | Technology | Purpose |
|---|---|---|
| Build Tool | Vite 7.3 | Fast dev server, optimized builds |
| UI Framework | Bootstrap 5.3 | Responsive components, grid system |
| State Management | Alpine.js 3.15 | Lightweight reactive data binding |
| Charts | Chart.js 4.5 | Interactive data visualization |
| Icons | Bootstrap Icons 1.13 | SVG icon library |
| Dates | Day.js 1.11 | Date formatting and manipulation |
| Modals | SweetAlert2 11.26 | Beautiful alert/confirm dialogs |
| Styling | SCSS (Sass 1.97) | CSS preprocessor |
| CSS Processing | PostCSS + Autoprefixer | Cross-browser compatibility |
| Component | Technology | Purpose |
|---|---|---|
| Framework | FastAPI 0.104 | High-performance async API |
| Server | Uvicorn 0.24 | ASGI server |
| ORM | SQLAlchemy 2.0 | Database abstraction |
| Validation | Pydantic 2.5 | Request/response validation |
| Database | SQLite (dev), PostgreSQL (prod) | Data persistence |
| Migrations | Alembic 1.13 | Database schema versioning |
| Testing | pytest 7.4 | Test framework |
| Async Driver | aiosqlite, asyncpg | Async database drivers |
All API endpoints are under /api/ prefix:
/api/purchases # Purchase CRUD
/api/warranties # Warranty CRUD
/api/retailers # Retailer management
/api/brands # Brand management
/api/analytics/* # Analytics endpoints
/api/export/* # Data export
/api/import/* # Data import
Success Response:
{
"id": "uuid",
"product_name": "iPhone 15 Pro",
"price": 999.99,
"status": "RECEIVED",
"purchase_date": "2026-01-15",
"created_at": "2026-01-15T10:30:00Z"
}Error Response:
{
"detail": "Purchase not found"
}Paginated Response:
{
"items": [...],
"total": 50,
"skip": 0,
"limit": 20
}The Settings API uses a hybrid storage approach - database-persisted settings for cross-device consistency, and localStorage for device-specific preferences.
Endpoints:
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/settings/ |
Get all settings with defaults |
| PUT | /api/settings/ |
Update settings (partial updates supported) |
| GET | /api/settings/{key} |
Get a specific setting by key |
| POST | /api/settings/reset |
Reset all settings to defaults |
Settings Storage Strategy:
| Setting | Storage | Reason |
|---|---|---|
currency_code |
Database + localStorage | Cross-device consistency |
date_format |
Database + localStorage | Cross-device consistency |
- short |
"Jan 30, 2026" format | |
- MM/DD/YYYY |
01/30/2026 format | |
- DD/MM/YYYY |
30/01/2026 format | |
- YYYY-MM-DD |
2026-01-30 format | |
- DD.MM.YYYY |
30.01.2026 format | |
language |
localStorage only | Device preference |
cardVisibility |
localStorage only | Device preference |
notifications |
localStorage only | Device preference |
theme |
localStorage only | Device preference |
Data Flow:
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Settings │────▶│ Backend API │────▶│ Database │
│ Page │ │ /api/settings│ │ (SQLite) │
└──────────────┘ └──────────────┘ └──────────────┘
│ ▲
│ localStorage (sync) │
▼ │
┌──────────────┐ ┌──────────────┐ │
│ localStorage │◀────│ Other Pages │────────────┘
│ (cache) │ │(read-only) │
└──────────────┘ └──────────────┘
Request/Response Examples:
# Get settings
GET /api/settings/
Response:
{
"currency_code": "USD",
"date_format": "MM/DD/YYYY"
}
# Update settings
PUT /api/settings/
Body: {"currency_code": "EUR", "date_format": "DD/MM/YYYY"}
Response:
{
"currency_code": "EUR",
"date_format": "DD/MM/YYYY",
"timezone": "America/New_York"
}
# Reset to defaults
POST /api/settings/reset
Response:
{
"currency_code": "USD",
"date_format": "MM/DD/YYYY"
}Date Format Usage:
All date displays respect the user's preferred format:
- Inventory table - Uses user preference
- Dashboard - Uses user preference
- Date inputs - Show format hint below field (e.g., "Format: 30.01.2026")
- API - Always uses ISO
YYYY-MM-DDfor data exchange
Current: None (admin dashboard for personal use)
Future: Can add JWT tokens or OAuth2
1. User fills form in inventory.html
↓
2. Alpine.js captures form data
↓
3. JavaScript sends POST to /api/purchases
↓
4. FastAPI validates with Pydantic schema
↓
5. PurchaseService creates record
↓
6. SQLAlchemy inserts into database
↓
7. FastAPI returns JSON response
↓
8. Alpine.js updates UI
↓
9. Toast notification shows success
1. Browser loads index.html
↓
2. Alpine.js initializes
↓
3. DashboardManager calls multiple APIs in parallel:
- GET /api/analytics/summary
- GET /api/analytics/spending
- GET /api/analytics/warranties/timeline
- GET /api/analytics/retailers
- GET /api/analytics/brands
- GET /api/analytics/top-products
- GET /api/analytics/recent-purchases
↓
4. Chart.js renders visualizations
↓
5. Alpine.js binds data to UI
Single server runs both frontend and backend
# Build frontend
npm run build
# Deploy backend (serves both API and static files)
cd backend
docker build -t spends-tracker .
docker run -p 80:8000 spends-trackerPros:
- Simple deployment
- One domain
- No CORS issues
Cons:
- Can't scale independently
- Backend must serve static files
- Single point of failure
Frontend on CDN, Backend on server
# Deploy frontend to Netlify
npm run build
netlify deploy --prod --dir=dist-modern
# Deploy backend to Railway
cd backend
railway upPros:
- ✅ Frontend on CDN (fast, free)
- ✅ Backend scales independently
- ✅ Lower costs
- ✅ Better performance
Cons:
- Need to configure CORS
- Two separate deployments
docker-compose upRuns:
- PostgreSQL on port 5432
- Backend on port 8000
- Frontend served by backend
This is an admin dashboard for personal use, so authentication is not implemented. Deploy behind:
- VPN
- IP whitelist
- Reverse proxy with basic auth
To add authentication:
- Add JWT token generation:
from fastapi import Depends, HTTPException
from fastapi.security import HTTPBearer
security = HTTPBearer()
@app.post("/api/login")
async def login(credentials: LoginSchema):
# Verify credentials
token = create_jwt_token(user_id)
return {"access_token": token}- Protect routes:
@app.get("/api/purchases", dependencies=[Depends(verify_token)])
async def get_purchases():
...- Frontend stores token:
localStorage.setItem('token', response.access_token);
// Add to all requests
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
}- Code Splitting - Vite automatically splits vendor code
- Asset Hashing - Cache busting with content hashes
- Lazy Loading - Charts load on demand
- CSS Minification - SCSS compiled and minified
- Image Optimization - WebP with PNG fallback
- Font Optimization - Google Fonts with display=swap
- Async/Await - Non-blocking I/O with async SQLAlchemy
- Connection Pooling - Database connection reuse
- Query Optimization - Eager loading of relationships
- Pagination - Limit results to 20 per page
- Caching - (Future) Redis for analytics queries
Current: Manual testing Future:
- Unit tests with Vitest
- E2E tests with Playwright
Current:
- Unit tests with pytest
- Integration tests for API endpoints
cd backend
pytestCoverage:
- test_purchases.py - CRUD operations
- test_analytics.py - Analytics calculations
- TODO: test_warranties.py, test_imports.py
- Frontend Changes
# Edit src-modern/scripts/components/
npm run dev # See changes instantly with HMR- Backend Changes
cd backend
# Edit app/routes/ or app/services/
# Server auto-reloads with --reload flag- Database Changes
cd backend
# Edit app/models/
alembic revision --autogenerate -m "Add field"
alembic upgrade head- Testing
# Backend tests
cd backend && pytest
# Frontend tests (future)
npm run test- Build & Deploy
npm run build
cd backend && docker build -t spends-tracker .- Add missing test files (test_warranties.py, test_imports.py)
- Add frontend unit tests
- Implement build-time HTML templating (avoid duplication)
- Add API response caching
- Add authentication (JWT)
- Add user management
- Add data backup/restore
- Add export to PDF
- Add email notifications for expiring warranties
- Mobile app (React Native or Flutter)
- Real-time updates (WebSockets)
- Multi-user support
- Receipt image upload and OCR
- Integration with retailers (Amazon API)
Check:
- Is
dist-modern/built? Runnpm run build - Is backend running? Check http://localhost:8000/api
- Check browser console for errors
Check:
- Backend running on correct port (8000)
- CORS configuration in backend/app/main.py
- Network tab in browser DevTools
Check:
- Database file exists:
backend/spends_tracker.db - Run migrations:
cd backend && alembic upgrade head - Check database logs
See CONTRIBUTING.md for development guidelines.
Architecture Version: 2.1 Last Updated: February 7, 2026 Status: ✅ Production Ready