intermediate one sitting personal-finance
Build your own group expense splitter (a personal Splitwise)
You will build a small web app where a household or trip group logs shared expenses and gets back the shortest list of repayments that settles everyone up. There are no accounts: a group lives at an unguessable link, and whoever holds the link can log and settle. Splitwise survives because the whole group already has it installed and its quiet reminders do the chasing, which is adoption rather than math. You are building for the people who share your fridge, and that is a perfectly good audience.
What you'll learn
- Modeling a shared ledger in SQLite with integer-cent arithmetic
- Minimum cash flow debt simplification: turning a tangle of IOUs into the fewest repayments
- Link-based access control with unguessable IDs instead of passwords
- Flexible split logic for equal, exact, percentage and weighted shares
- Multi-currency bookkeeping with a hand-maintained rate table, plus CSV and JSON export
Before you start
- Node.js 22 and npm installed locally
- Git and an account on a code host such as GitHub
- A free-tier account on any small host (PaaS or a low-cost VPS) for the deploy step
- Comfort running commands and editing files in a terminal; no accounting background needed
The build
Hand the agent the boring foundation: an Express app with SQLite storage, server-rendered pages, and one-click groups that live at unguessable URLs. Members are just names typed into the group. When it finishes, run the server, create a group, and confirm the URL looks random.
step prompt
Build a link-shared expense splitter scaffold. Requirements: - Node 22 with Express, better-sqlite3, server-rendered EJS templates, htmx copied into public/vendor, no SPA framework and no build step - SQLite file at ./data/app.db with the path read from DATABASE_PATH in .env, commit a .env.example and gitignore .env - Create tables: groups (id TEXT primary key, name TEXT, base_currency TEXT, created_at TEXT) and members (id INTEGER primary key, group_id TEXT, name TEXT) - POST /groups creates a group whose id comes from nanoid at 21 characters, then redirects to /groups/<id> - GET /groups/<id> lets anyone add members as plain typed names, no signup screens of any kind - README states plainly that the unguessable link is the entire auth model - Pain warning: better-sqlite3 compiles native code, so if npm install fails check for a C compiler and python3 before suspecting the code - Out of scope: expense forms, balance math, styling, deployment
Drive the assistant through the heart of the app: recording who paid and how the cost divides. Work with it to validate four split modes and store resolved cents per person. Test by adding a pizza night split three ways, then edit it and check the activity log caught the change.
step prompt
Add expense logging with flexible splits to the splitter from step 1. Requirements: - Each expense stores payer member id, amount in integer cents, currency code, ISO date, note and category - Support four split modes: equal, exact amounts, percentages and shares, validating that exact amounts sum to the total and percentages sum to 100 within a one cent tolerance - Persist resolved results in an expense_splits table (expense_id, member_id, value_cents) so later steps read final numbers rather than raw mode inputs - An htmx form posts to POST /groups/<id>/expenses and swaps in an updated expense list fragment, no full page reload - Every edit and delete appends a row to an activity_log table (group_id, entity, entity_id, action, created_at) - Log repayments as expenses with kind set to reimbursement so they can net against balances later - Pain warning: float money will bite, keep every amount and split as integer cents including form parsing - Out of scope: receipt photos, per-item splits, recurring expenses
This is the satisfying part: collapsing everyone's tangled IOUs into the fewest payments. Have the assistant implement the greedy creditor-debtor pairing, then you write the circular-debt test yourself and watch it collapse to zero transfers. Read the GeeksforGeeks walkthrough first if the algorithm feels opaque.
step prompt
Add the balances view and the settle-up plan to the existing app. Requirements: - GET /groups/<id>/balances shows each member's net position: total paid minus total owed across expense_splits, with reimbursements netted in - Write a simplifyDebts function implementing minimum cash flow debt simplification, repeatedly pairing the largest creditor with the largest debtor until every net reaches zero - Render the result as a plain transfer list, one row per payment showing payer, recipient and amount, no buttons - Add a unit test where A owes B, B owes C and C owes A, asserting the plan collapses to zero transfers - Cover edge cases: a single-member group, and an equal split whose one cent remainder lands on the payer - Pain warning: the greedy approach gives few transfers in practice but is not provably optimal, resist rewriting it as a graph flow problem - Out of scope: interest calculations, date-aware settlement, any payment processing
Trips spend in several currencies, so give each group a hand-edited rate table and convert only when balancing. Then wire CSV and JSON endpoints so the group's data always leaves cleanly. Verify by adding one expense in another currency and confirming the balance converts once, not twice.
step prompt
Add multi-currency handling and exports to the group splitter. Requirements: - Keep every expense amount in its original currency and add a rates table keyed by group and currency storing hand-entered rates to the group's base currency - Build a small settings section on the group page where members edit the rate table, with no external exchange rate API calls - Convert to the base currency only during balance computation, rounding once per member there - On the expense list show original amount and converted base amount side by side - Add GET /groups/<id>/export.csv and GET /groups/<id>/export.json streaming members, expenses, expense_splits and rates - Fix the CSV column order and document it in the README so future imports stay predictable - Pain warning: converting per display instead of per computation makes pennies drift between views, convert exactly once - Out of scope: live rates, historical rate lookups, automatic currency detection
Create a small server or free-tier app on the host of your choice, point a subdomain's DNS record at it, and enable HTTPS. Set DATABASE_PATH as a host environment variable, mount persistent storage for the data directory, and schedule a nightly backup of app.db. Send the link to your group and log the first real expense together.
What you won't get
- A web app you can add to a phone home screen; native iOS and Android builds stay out of scope
- Your version starts with a blank member list; friends keep their Splitwise accounts and habits elsewhere
- Currency conversion runs on rates you type in yourself and refresh by hand
- Receipt scanning and itemized per-dish splits are deliberately excluded
- Settlement ends at a who-pays-whom plan; payments themselves happen in person or in your payment app
Why people still pay — and what that teaches you
network-effects: Splitwise wins because everyone's flatmate already has it installed, so the group picks the tool, not the individual. Builders learn that shipping to a group you already coordinate with beats adding more features.
execution-polish: Polished apps and quiet balance reminders keep debts moving without awkward conversations. Builders learn that software doing the nagging is real product substance and worth copying in spirit.
integrations: Pro sells receipt scanning, automatic currency conversion and settle-up hooks into payment apps. Builders learn these connectors are the paid surface, and naming them out of scope keeps a personal build honest.
Stretch goals
- Add a PWA manifest so the app installs to a phone home screen
- Give each member a personal link that highlights only their own balance
- Write an importer for a Splitwise CSV export so one real group can migrate over
All steps done — did it work?
Congratulations. Tell someone what you built.
About Splitwise
Splitwise costs $5/month. Splitwise sits where the friction is lowest: everyone already has an account, the apps are polished, and the balance quietly nags people so you do not have to. Pro mostly sells relief from the free tier's daily expense cap and the ads, plus receipt scanning and automatic currency conversion for trips. The subscription monetizes the fact that the group, not you, picks the tool.
Sources & further reading
- Minimize cash flow among friends (GeeksforGeeks) — Explains the exact debt-simplification algorithm behind your settle-up plan
- Spliit (source code) — An open-source reference implementation worth reading when data modeling gets tricky
- Spliit (hosted) — Try a finished link-shared splitter before building your own
- IHateMoney — A long-running self-hosted splitter, useful for comparing scope decisions
Finished alternatives (if you'd rather not build)
- Spliit — Share a link, split, settle. No accounts, no ads. Use their instance or run your own.
- Tricount — Free and huge in Europe; closed source and bunq-owned, new signups get a bunq account, and CSV export left with the old Premium.
- IHateMoney — Around since 2011, proudly boring, in declared maintenance mode; use their free instance or one docker run.
- Cospend — Splitwise for people who already run Nextcloud; pairs with the MoneyBuster Android app.
Keep building
New lessons and honest build notes, by email. No spam, one-click out.
Signups open when the site goes live.