Case study
Expense Tracker

Expense Tracker is a solo full-stack project for splitting shared costs within a group — rent, trips, dinners — without the usual spreadsheet math. People create a group, add expenses with equal, exact, or percentage splits, and the app tracks who owes whom. The part that made it worth building rather than using an existing app: instead of just listing every individual debt, it reduces them down to the fewest possible payments that settle the whole group.
The backend is Next.js API routes on a Prisma/PostgreSQL schema (users, groups, members, expenses, splits), with its own JWT-based auth using jose and bcrypt for password hashing rather than an off-the-shelf auth library. Every request body is validated with Zod, and a shared AppError type plus a withErrorHandling wrapper turn expected failures into consistent, specific API error responses instead of generic 500s.
The core logic — split calculation and settlement — is isolated in plain TypeScript modules with a Vitest suite, deliberately kept framework-agnostic so it's cheap to test. The settlement function is a greedy minimum-cash-flow algorithm: repeatedly matching the largest creditor with the largest debtor until every balance is within a cent of zero. One test locks in a real bug I hit in production — an EXACT split that didn't sum to the total was crashing with an unhandled error — it now fails cleanly with a specific message instead.
Most of a later pass went into UI polish that isn't visible in the API: a custom Modal/Toast/Confirm system built on React context so no screen ever calls window.alert or window.confirm, loading and disabled states on every button that triggers a request, a from-scratch Dropdown and Tooltip component replacing native <select> menus and title attributes that couldn't be restyled to match the dark theme, a one-click Settle up action that records every payment in a multi-transaction settlement plan at once, and a small dark, avatar-and-badge design system shared across the dashboard and group pages.
The trickiest feature was locking an expense once it's been paid off, because a recorded payment only settles a group's overall balance — it doesn't reference any specific expense. My first pass locked every expense in a group as soon as any payment existed, which was too broad; a tighter version locked any expense sharing a participant with a payment, which was still wrong, since it could lock an expense over a payment between the same two people that had nothing to do with it. What actually fixed it was adding a real link — a PaymentAllocation table that attributes each payment to the specific expense(s) it covers, computed automatically oldest-shared-expense-first the moment a payment is recorded — so an expense only locks once there's an actual payment tied to it, not a heuristic guessing at one.
Visit Expense Tracker ↗