Skip to content
Alvaro Sarria
Menu
← All work

Fair-Share

Group expense tracker with JWT auth, server-side balance settlement and a category breakdown, split across a React client and an Express/MongoDB API.

Problem

Splitting shared expenses across a group — rent, trips, shared subscriptions — usually ends up in a spreadsheet nobody trusts. I wanted a tool that tracked who owes what automatically, without manual balance math.

Approach

Two repositories: a React client and a Node/Express REST API backed by MongoDB. Users sign up, create groups, log expenses against them choosing who paid and who the cost splits between, and every balance is derived from that ledger rather than entered by hand. Supabase Storage holds the images so the API never handles file uploads.

Architecture

  • Client: React 19 + Vite, React Router 7 with every route past the landing page behind React.lazy. All requests go through a single configured axios instance that attaches the bearer token and, on a 401, clears it and redirects to login — components pass paths, never URLs
  • Server: Express 5 and Mongoose over MongoDB Atlas, structured around resource routes (auth, users, groups, expenses) with bcrypt password hashing and centralized error handling
  • Balance maths: one module, expenseCalculations.js, exposing per-user totals and per-member group balances — the single source of truth for every figure the UI renders
  • Storage: Supabase Storage for profile, group and receipt photos

Key decisions

  • Balance calculations live in one module rather than being recomputed ad hoc per component, so the number a user sees on the overview, on a group card and in the group header is always the same number
  • Authorization tightens per action rather than sitting at the router: some routes are self-only, some member-only, some author-only, and each returns a real 401/403/404 instead of always responding 200
  • Password hashes are excluded from every API response by default and only re-selected internally to verify a login
  • Group admin rights are transferable, because a group outlives whoever happened to create it

Results

A deployed expense manager with JWT-authenticated accounts and protected routes, groups with transferable admin, per-expense payer and split selection, paid/borrowed/net balances for both the individual and the group, a two-tone balance bar repeated across the overview and every group card, a donut chart of spending by category, image uploads, a navigation rail that collapses to a drawer on mobile, and labelled controls with visible focus rings and prefers-reduced-motion support.

What I’d do differently

Neither repository has a test framework, which is the first thing I’d add now that the balance maths is centralized enough to be worth testing. I’d also normalize the API payloads: expenseAuthor and expenseUsers come back as ID strings from list endpoints but as populated objects from detail endpoints, and every consumer has to handle whichever shape it is handed.