Track the hangout, not the math.
π Live Demo Β· Features Β· Algorithm Β· Getting Started
| Landing & Login | Active Hangouts |
![]() |
![]() |
| Hangout Detail & Settlements | Expense & Payment History |
![]() |
![]() |
Every student group trip ends the same way β nobody knows who owes what, the mental math doesn't add up, and the WhatsApp messages go on for days.
Rough Bills solves this without a spreadsheet.
Create a hangout, share a room code, log who paid for what β and Rough Bills calculates the fewest number of payments needed for everyone to settle up. No more overcounting. No more awkward debt arithmetic. When the hangout closes, final dues are locked in and members can mark payments against them.
The design is intentional: warm paper textures, hand-drawn aesthetic, dashed borders. Most expense apps feel like bank software. This one feels like something you'd sketch on a napkin β because that's exactly what the problem is.
- Hangout rooms β Create a session with a name, get a short room code, and share it with the group. No email required to join.
- Real-time expense logging β Any member logs an expense (paid by whom, split across whom), and the running balances update immediately.
- Minimum cash flow settlements β When a hangout closes, the app calculates the mathematically optimal settlement plan β the fewest number of transfers needed to make everyone whole.
- Post-hangout payment tracking β After closing, members mark individual payments against the final dues. Balances update as payments clear.
- Hangout history β Every closed hangout is archived with its full expense log, settlement plan, and payment trail.
- Username-only auth β No email required. Create an account with just a username and password β keeps the UX fast and friction-free.
The core settlement logic uses a minimum cash flow approach:
- After all expenses are logged, each member's net position is calculated: what they paid out minus what they owe.
- Members with a positive net position are creditors; members with a negative net position are debtors.
- A greedy algorithm matches the largest debtor with the largest creditor, records the transfer, and repeats until all balances clear.
This minimizes the total number of transactions needed β a group of n members always settles in at most n - 1 transfers, regardless of how many individual expenses were logged.
The implementation lives in server/src/lib/calculations/ and is fully unit-tested (11 tests, 0 failures):
β splits a single expense equally across two members
β handles multiple expenses with alternating payers
β greedy matching reduces transfers for multiple creditors and debtors
β omits zero-net users from balances and settlements
β distributes remainder deterministically by member order
β returns no balances and no settlements for empty expense arrays
β rejects expenses whose payer is not a hangout member
β calculateDirectSettlements preserves pairwise dues while netting opposite-direction spend
β full payment clears the outstanding due
β partial payment only reduces the remaining due
β buildArchivedDebtSummary uses only remaining closed-hangout dues
| Layer | Choice |
|---|---|
| Frontend | React 19 + Vite + Tailwind CSS |
| Backend | Node.js + Express |
| Database | PostgreSQL (Neon) + Prisma ORM |
| Auth | JWT (username-based, no email required) |
| Hosting | Vercel (frontend) Β· Railway or Render (backend) |
- Node.js 20+
- A PostgreSQL database URL (Neon recommended for free tier)
git clone https://github.com/raajpatre/rough-bills.git
cd rough-bills
npm installCreate a root .env:
DATABASE_URL="postgresql://..."
JWT_SECRET="your-secret-here" # generate with: openssl rand -hex 32
CLIENT_ORIGIN="http://localhost:5173"
PORT=4000npm run prisma:generate
npm run dev:server
npm run dev:clientFor first-time database setup:
npm run prisma:migrate --workspace serverFrontend (Vercel)
- Root directory:
client - Build command:
npm run build - Output directory:
dist - Environment variable:
VITE_API_BASE_URL=https://your-backend-domain.com/api
Backend (Railway / Render)
- Root:
server - Start command:
node src/server.js - Environment variables:
DATABASE_URL,JWT_SECRET,CLIENT_ORIGIN,NODE_ENV=production,PORT
MIT β open to use, adapt, and deploy.
If Rough Bills saved you from a group chat argument, consider dropping a β β it helps.
Made with β€οΈ by raajpatre




