A mini Splitwise: group expense tracking, settle-up math, and recurring bills for roommates and trips.
I built the backend with NestJS, Prisma, and PostgreSQL, with JWT access tokens paired with rotating
refresh tokens — hashed with SHA-256 and never stored raw, so a database leak alone can't be replayed —
and Resend for invite emails. For settle-up, I wrote a greedy min-cash-flow algorithm instead of
brute-forcing the NP-hard optimal solution: two max-heaps, largest creditor paired with largest debtor
each round, collapsing a group's tangled debts into a minimal set of suggested payments. See
backend/src/settlements/settle-up.util.ts.
On the frontend I used React + TypeScript, Vite, and Tailwind, with React Hook Form + Zod for forms and TanStack Query for server state, including a single axios interceptor that handles silent access-token refresh with single-flight de-duplication so concurrent 401s don't trigger a refresh stampede.
Both sides are covered by real test suites — service-level unit tests with Prisma mocked, plus an end-to-end suite that drives the full HTTP stack against a real Postgres database, not mocks — wired into CI via GitHub Actions on every push.
backend/ NestJS API (Prisma + PostgreSQL)
frontend/ React + Vite SPA
docker-compose.yml local PostgreSQL for development
render.yaml Render blueprint for deploying the backend
Prerequisites: Node.js 22+, Docker.
- Start Postgres:
It publishes on host port 5433 rather than 5432, to stay out of the way of a system Postgres — but that collides just as easily with another project that made the same choice, so the port is overridable with
docker compose up -d
POSTGRES_HOST_PORT(see.env.example). If you move it,backend/.env'sDATABASE_URLhas to name the same port. - Backend:
API runs at
cd backend cp .env.example .env npm install npx prisma migrate dev npm run start:devhttp://localhost:3000/api. Without aRESEND_API_KEY, invite emails are logged to the console instead of sent, so invites still work end-to-end in dev. - Frontend (in a second terminal):
App runs at
cd frontend cp .env.example .env npm install npm run devhttp://localhost:5173.
Run the backend unit tests with npm test from backend/, or the full e2e suite (needs the Postgres
container running) with npm run test:e2e. Frontend tests run with npm run test from frontend/.
- Database: create a free Neon Postgres project and copy its connection string.
- Backend (Render): this repo includes a
render.yamlblueprint. In the Render dashboard, "New +" → "Blueprint", point it at this repo, and it will buildbackend/Dockerfile. Either let Render provision its own free Postgres (already wired intorender.yaml) or delete thedatabases:block and setDATABASE_URLmanually to your Neon connection string. SetRESEND_API_KEY,INVITE_FROM_EMAIL, andFRONTEND_URL(your Vercel URL) in the Render dashboard — they're markedsync: falseso they aren't committed to the repo. Railway works the same way without a config file: create a service from this repo with root directorybackend, it detects the Dockerfile automatically, and env vars are set in the Railway dashboard. - Frontend (Vercel): import this repo, set the project root to
frontend/, and setVITE_API_URLto your deployed backend's URL (e.g.https://your-api.onrender.com/api).vercel.jsonhandles the SPA rewrite so client-side routes work on refresh.
After deploying, update the backend's FRONTEND_URL to the live Vercel URL (needed for CORS and invite
links) and redeploy.