# |
SECTION |
LINK |
|---|---|---|
| 01 | About the Project | PROJECT.ABOUT |
| 02 | Feature Overview | FEATURES.DECK |
| 03 | Technology Stack | STACK.LOAD |
| 04 | Repository Layout | PROJECT.STRUCTURE |
| 05 | Quickstart | QUICKSTART.SETUP |
| 06 | Development Guide | DEV.WORKFLOW |
| 07 | API Surface | ROUTES.MAP |
| 08 | Security & Data | SECURITY.MODEL |
| 09 | Roadmap | ROADMAP |
| 10 | Contribution Guide | CONTRIBUTION.PROTOCOL |
| 11 | Visual Identity | DESIGN.AESTHETIC |
| 12 | License & Support | SUPPORT.LICENSE |
Spendly is a personal expense tracking web application built with Flask. It helps you log expenses, spot spending patterns, and stay on budget β without the spreadsheet headache.
Users can register, sign in, add expenses with category, amount, date, and description, then visualise their spending through category breakdowns, monthly summaries, and date-range filters on a personal profile dashboard.
Built with Flask + Jinja2 for server-side rendering, SQLite for zero-config storage, and custom CSS with design tokens for a warm editorial aesthetic. Features automatic dark/light theme detection with a manual toggle, CSRF-protected forms, and Werkzeug password hashing.
PROBLEM |
SOLUTION |
|---|---|
| Spreadsheets are tedious and error-prone | One-click expense logging with structured fields |
| No insight into spending habits | Category breakdowns, recent transactions, and date filtering |
| Complex finance apps are overkill | A clean, focused, personal tracker |
FEATURE |
DESCRIPTION |
STATUS |
|---|---|---|
| π User Accounts | Register, sign in, sign out with Werkzeug-hashed passwords | β
LIVE |
| π° Log Expenses | Add any expense in seconds with all details | β
LIVE |
| βοΈ Edit Expenses | Update any field of an existing expense | β
LIVE |
| ποΈ Delete Expenses | Remove expenses with CSRF-protected POST | β
LIVE |
| οΏ½ Category Breakdown | Per-category totals and share of spending | β
LIVE |
| π Profile Dashboard | Summary stats, recent transactions, category cards | β
LIVE |
| ποΈ Date Filtering | Preset ranges (this month, 3 months, 6 months) + custom range | β
LIVE |
| π Analytics Page | Dedicated analytics view | β
LIVE |
| π‘οΈ CSRF Protection | Token-based form protection on every state-changing request | β
LIVE |
| π Theme Support | Automatic dark/light detection + manual toggle | β
LIVE |
| π± Responsive Design | Works on desktop and mobile devices | β
LIVE |
| π§ͺ Test Suite | pytest + pytest-flask covering every shipped feature | β
LIVE |
LAYER |
TECHNOLOGY |
PURPOSE |
|---|---|---|
| Backend | Flask 3.1.3 / Werkzeug 3.1.6 | Routing, sessions, password hashing |
| Language | Python 3.13 | Core application logic |
| Storage | SQLite (stdlib sqlite3) |
Local, zero-config database |
| Templating | Jinja2 | Dynamic HTML generation |
| Forms | CSRF tokens + flask.session |
Cross-site request forgery protection |
| Frontend | CSS3 + vanilla JS | Design tokens, theme toggle |
| Testing | pytest 8.3.5 + pytest-flask 1.3.0 | Feature-level integration tests |
| Tooling | uv | Dependency + environment management |
expense-tracker/
βββ app.py β Flask app, all routes, CSRF middleware
βββ pyproject.toml β Project metadata + dependencies
βββ uv.lock β uv lockfile for reproducible installs
βββ .python-version β Python version pin (3.13)
βββ opencode.json β OpenCode agent/command config
β
βββ database/
β βββ __init__.py
β βββ db.py β init_db, seed_db, CATEGORIES, get_db
β βββ queries.py β CRUD + analytics SQL helpers
β
βββ static/
β βββ css/
β β βββ style.css β Global styles + design tokens
β β βββ landing.css β Landing page styles
β βββ js/
β βββ main.js β Theme toggle + shared JS
β
βββ templates/
β βββ base.html β Shared navbar + footer
β βββ landing.html β Marketing landing page
β βββ register.html β Registration form
β βββ login.html β Sign-in form
β βββ profile.html β User dashboard (stats, transactions, categories)
β βββ analytics.html β Analytics view
β βββ add_expense.html β Create-expense form (CSRF)
β βββ edit_expense.html β Edit-expense form (CSRF)
β βββ terms.html β Terms and Conditions
β βββ privacy.html β Privacy Policy
β
βββ tests/
β βββ test_backend_connection.py β Smoke tests for the app boot
β βββ test_date_filter.py β Profile date-filter helpers
β βββ test_06-date-filter-profile.py
β βββ test_07-add-expense.py β Add-expense flow (validation, CSRF, DB)
β βββ test_08-edit-expense.py β Edit-expense flow (ownership, validation)
β βββ test_09-delete-expense.py β Delete-expense flow (CSRF, ownership)
β
βββ seed_user.py β Seed a demo user
βββ seed_expenses.py β Seed demo expenses
βββ expense_tracker.db β Local SQLite database (gitignored)
- Python 3.13 β pinned via
.python-version - uv β fast dependency manager (recommended)
# 1. Clone the repo
git clone https://github.com/SalikAhmad702/expense-tracker.git
cd expense-tracker
# 2. Create venv and install dependencies
uv sync
# 3. Start the development server
python app.pyThe database is initialised and seeded automatically on app boot β no manual step required.
# 1. Clone the repo
git clone https://github.com/SalikAhmad702/expense-tracker.git
cd expense-tracker
# 2. Create and activate a virtual environment
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# 3. Install dependencies
pip install -e .
# 4. Start the development server
python app.pyOpen
http://localhost:5001in your browser π
TASK |
COMMAND |
|---|---|
| Run the app | python app.py |
| Run the full test suite | pytest |
| Run a single test file | pytest tests/test_07-add-expense.py |
| Initialise the database only | python -c "from database.db import init_db; init_db()" |
| Seed the database only | python -c "from database.db import seed_db; seed_db()" |
| Seed a demo user | python seed_user.py |
| Seed demo expenses | python seed_expenses.py |
| Sync dependencies | uv sync |
| Lint / format (via opencode) | configured in opencode.json |
π‘
app.pycallsinit_db()andseed_db()on startup, so the database is ready the first time you boot the server.
ROUTE |
METHOD |
DESCRIPTION |
STATUS |
|---|---|---|---|
/ |
GET | Landing page | β
ACTIVE |
/register |
GET, POST | Create an account | β
ACTIVE |
/login |
GET, POST | Sign in | β
ACTIVE |
/logout |
GET | Sign out and clear session | β
ACTIVE |
/profile |
GET | User dashboard (stats, transactions, categories, filters) | β
ACTIVE |
/analytics |
GET | Analytics view | β
ACTIVE |
/expenses/add |
GET, POST | Add a new expense (CSRF) | β
ACTIVE |
/expenses/<int:id>/edit |
GET, POST | Edit an existing expense (CSRF) | β
ACTIVE |
/expenses/<int:id>/delete |
POST | Delete an expense (CSRF) | β
ACTIVE |
/terms |
GET | Terms and Conditions | β
ACTIVE |
/privacy |
GET | Privacy Policy | β
ACTIVE |
All mutating endpoints are gated by @app.before_request CSRF middleware and require a signed-in user (except /register and /login). Expense endpoints enforce ownership β a user can only see and mutate their own rows.
Spendly takes a pragmatic, dependency-light approach to security:
CONCERN |
MITIGATION |
|---|---|
| Password storage | werkzeug.security.generate_password_hash (PBKDF2 by default) |
| Password verification | Constant-time check_password_hash on login |
| Session integrity | Flask's signed session cookie; SECRET_KEY read from SPENDLY_SECRET_KEY env var |
| CSRF | Per-session token generated by @app.before_request; validated on every POST |
| Cookie scope | SESSION_COOKIE_SAMESITE = "Lax" |
| Authorisation | Expense routes check expense.user_id == session["user_id"] and 404 otherwise |
| Input validation | Server-side checks on amount (> 0, finite), date (YYYY-MM-DD), category (allow-list), description (β€ 200 chars) |
π In production, set
SPENDLY_SECRET_KEYto a long random value and run behind HTTPS.
- User registration, login, logout with password hashing
- Personal profile dashboard (stats, transactions, categories)
- Add / edit / delete expenses with CSRF protection
- Date filtering with presets + custom range
- Analytics view
- Dark / light theme with manual toggle
- pytest + pytest-flask test suite per feature
- CSV / Excel export of expenses
- Search across expense descriptions
- Monthly budget caps with progress bars
- Chart.js visualisations on the analytics page
- Password reset flow
- Email verification on registration
- Pagination on the transactions list
- Bulk delete
- Receipt attachments
- Recurring-expense templates
# Fork β Branch β Commit β Push β PR
git checkout -b feature/your-improvement
git add .
git commit -m "feat: describe your change"
git push origin feature/your-improvement- β Follow the existing code style
- β Describe what your PR changes and why
- β Keep commits focused and atomic
- π§ͺ Run
pytestbefore opening a PR β every shipped feature has a matching test file
Spendly uses a warm editorial aesthetic with:
ELEMENT |
DETAILS |
|---|---|
| π¨ Color Palette | Deep green accent (#1a472a), warm paper tones (#f7f6f3) |
| βοΈ Typography | DM Serif Display for headings, DM Sans for body text |
| π Theme Support | Automatic dark/light mode detection + manual override |
| π· Icons | Custom CSS icons with geometric shapes |
License: This project is currently unlicensed β no formal license file yet. Please reach out before using it commercially.
Found a bug? Have a feature idea? Open an Issue on GitHub β every report makes Spendly better.
If this project helped you with expense tracking or Flask development, consider giving it a β β it keeps the development going!
