// project
RoomMate
completedA shared-room expense tracker that splits rent, utilities, and everyday purchases fairly — even when coverage and custom splits get complicated.
Next.jsTypeScriptSupabasePostgreSQLTailwind CSSshadcn/uiRechartsVercel
// about
RoomMate is a full-stack expense-splitting app built for people sharing a room or flat, the kind of setup where rent, water, electricity, and groceries get divided among however many people happen to be living there, and a simple even split never quite matches reality.
The problem
Most bill-splitting apps assume every expense divides evenly across everyone, every time. Real shared housing doesn't work that way:
- Someone doesn't eat meat, so groceries with meat shouldn't split across everyone.
- Two roommates share one wallet, one "covers" the other, so their share should redirect, not double-count.
- Some purchases (like a gas refill) get paid for in cash on the spot, there's no debt to track, but it should still show up in spending history.
- Billing cycles aren't the calendar month, every household picks its own cycle-end date.
RoomMate handles all of this as first-class features, not edge cases.
Key features
- Households with 6-character invite codes
- Custom billing cycles with owner-triggered, member-approved close requests
- Fixed bills (rent, water, electricity) tracked historically
- Itemized, participant-scoped expenses, choose exactly who each purchase splits among
- One-directional coverage relationships (
pays_for), enforced server-side - Settled-on-the-spot expenses that count in spending stats without creating phantom debt
- Real settle-up balances via a greedy debt-simplification algorithm
- Immutable itemized receipts, exportable as PDF/PNG
- Personal + shared dashboards with Recharts insights
- Offline-first shopping mode with background sync
- Installable PWA
Notable engineering details
- Row Level Security on every table, with a few
SECURITY DEFINERRPCs for the cases plain RLS can't express safely (e.g. joining a household by invite code). - Split calculations are computed once, server-side, and reused consistently across every view, an early version duplicated this logic per-view, which caused numbers to visibly disagree with each other until it was consolidated into one source of truth.
- Deployed functions are region-pinned to match the database region, cutting the cross-continental round-trip latency that was previously the dominant cause of slow page loads.