PingToProd

Architecture Document

RoomieTab

Architecture
All Artifacts

Architecture Document

roomietab schema

Overview

RoomieTab is a real-time roommate expense management web app for groups of up to 5 people. Built on Next.js 15 (App Router) with Supabase as the backend, it enables frictionless expense logging with flexible split rules (equal, exact, percentage, shares), tracks who paid for what, and at month-end computes the minimum number of transactions to settle all debts using a greedy net-balance algorithm.

Architecture highlights:

  • Database: 7 tables in a dedicated 'roomietab' PostgreSQL schema. All monetary values stored as integer cents to eliminate floating-point errors. Row-Level Security on every table ensures strict household-scoped data isolation.
  • Auth: Supabase Auth with email magic links (zero-friction) and Google OAuth. Guest members can join via shareable invite link without mandatory account creation.
  • Realtime: Supabase Realtime subscriptions on expenses and settlement_transactions tables provide live sync across all connected roommates — no manual refresh needed.
  • API Design: Most reads use direct Supabase client queries (protected by RLS). API routes handle complex server-side logic: settlement calculation (min-transaction algorithm), month archival, recurring expense generation, and file exports.
  • Storage: Supabase Storage bucket for receipt images with 5MB limit and image-only MIME type restrictions.
  • Performance: Next.js Server Components for fast initial loads, optimistic UI updates for instant feedback, and efficient composite indexes on high-query paths (household+date, household+month).

Tech Stack

framework

Next.js

framework Version

15.1

styling

Tailwind CSS 3.4 with custom design system (indigo primary, Caveat/Inter fonts, hand-drawn shadow utilities)

database

Supabase PostgreSQL with dedicated 'roomietab' schema, Row-Level Security on all tables, integer-cents monetary storage

auth

Supabase Auth with email magic link and Google OAuth, PKCE flow, cookie-based sessions via @supabase/ssr

hosting

Vercel (Edge Runtime for API routes, automatic preview deployments on PR)

ci

GitHub Actions (lint, type-check, build on push and PR to main)

Data Entities

Household

roomietab.households
FieldTypeNullableDescription
iduuidnoPrimary key
nametextnoHousehold display name e.g. Maple House
invite_codetextnoUnique shareable invite code for joining via link
created_byuuidnoFK to auth.users — the household creator/admin
created_attimestamptznoCreation timestamp
updated_attimestamptznoLast update timestamp
one-to-manyMemberone-to-manyExpenseone-to-manySettlementone-to-manyRecurringTemplate

Member

roomietab.members
FieldTypeNullableDescription
iduuidnoPrimary key
household_iduuidnoFK to households
user_iduuidyesFK to auth.users — null for guest members who haven't signed up
display_nametextnoName shown in the app UI
emailtextyesEmail used for invitations
avatar_urltextyesProfile photo URL from Supabase Storage
roletextnoEither 'admin' or 'member'
venmo_handletextyesVenmo username for pre-filled payment deep links
paypal_emailtextyesPayPal email for pre-filled payment deep links
notification_prefsjsonbnoPush notification toggle preferences
is_activebooleannoSoft-delete flag for removed members
joined_attimestamptznoWhen member joined the household
updated_attimestamptznoLast update timestamp
one-to-manyExpenseone-to-manyExpenseSplit

Expense

roomietab.expenses
FieldTypeNullableDescription
iduuidnoPrimary key
household_iduuidnoFK to households
descriptiontextnoShort expense label e.g. 'Whole Foods run'
amount_centsintegernoTotal amount in cents (integer) to eliminate floating-point rounding errors
categorytextnoOne of: rent, utilities, groceries, dining, subscriptions, transport, household, other
split_typetextnoSplit method: equal, exact, percentage, shares
paid_by_member_iduuidnoFK to the member who paid this expense
expense_datedatenoDate the expense occurred
receipt_urltextyesURL to receipt image stored in Supabase Storage
is_recurringbooleannoFlag indicating if this was auto-generated from a recurring template
recurring_dayintegeryesDay of month for display purposes (1-31)
recurring_template_iduuidyesFK to the recurring_templates row that generated this
is_deletedbooleannoSoft-delete flag — deleted expenses hidden from UI but kept for audit
created_attimestamptznoCreation timestamp
updated_attimestamptznoLast update timestamp
one-to-manyExpenseSplit

ExpenseSplit

roomietab.expense_splits
FieldTypeNullableDescription
iduuidnoPrimary key
expense_iduuidnoFK to the parent expense
member_iduuidnoFK to the member who owes this share
amount_centsintegernoThis member's share in cents
percentagenumeric(5,2)yesPercentage value if split_type is percentage
sharesintegeryesNumber of shares if split_type is shares
created_attimestamptznoCreation timestamp

RecurringTemplate

roomietab.recurring_templates
FieldTypeNullableDescription
iduuidnoPrimary key
household_iduuidnoFK to households
descriptiontextnoExpense description template e.g. 'Netflix'
amount_centsintegernoAmount in cents
categorytextnoExpense category
split_typetextnoSplit method
paid_by_member_iduuidnoFK to default payer member
split_configjsonbnoJSON array of split member configs: [{memberId, amountCents?, percentage?, shares?}]
day_of_monthintegernoDay of month to auto-create expense (1-31)
is_activebooleannoWhether template is active (soft-delete)
last_generated_attimestamptzyesTimestamp of last auto-generated expense
created_attimestamptznoCreation timestamp
updated_attimestamptznoLast update timestamp

Settlement

roomietab.settlements
FieldTypeNullableDescription
iduuidnoPrimary key
household_iduuidnoFK to households
monthdatenoFirst day of the settlement month (e.g. 2025-02-01)
is_archivedbooleannoWhether the month has been fully archived/settled
archived_attimestamptzyesWhen the month was archived
archived_byuuidyesFK to member who performed the archive action
created_attimestamptznoCreation timestamp
updated_attimestamptznoLast update timestamp
one-to-manySettlementTransaction

SettlementTransaction

roomietab.settlement_transactions
FieldTypeNullableDescription
iduuidnoPrimary key
settlement_iduuidnoFK to the parent settlement
payer_member_iduuidnoFK to member who needs to pay
receiver_member_iduuidnoFK to member who receives payment
amount_centsintegernoTransaction amount in cents
is_settledbooleannoWhether this transaction has been completed
settled_attimestamptzyesWhen marked as settled
created_attimestamptznoCreation timestamp

API Routes

POST/api/householdsauth

Create a new household with the authenticated user as admin. Creates household row, initial member row, and generates invite code.

PUT/api/households/[id]auth

Update household name. Only admin members can update.

POST/api/households/[id]/joinauth

Join a household via invite code. Creates a new member record linked to the authenticated user. Enforces max 5 members.

POST/api/households/[id]/inviteauth

Send email invitations to roommates. Only admin can invite.

POST/api/expensesauth

Create expense with splits in a single transaction. Validates split amounts sum to total. Inserts expense + expense_splits atomically.

PUT/api/expenses/[id]auth

Update an existing expense and its splits. Deletes old splits and re-creates. Validates user is household member.

DELETE/api/expenses/[id]auth

Soft-delete an expense by setting is_deleted=true. Does not remove data for audit trail.

POST/api/settlements/calculateauth

Compute minimum settlement transactions for a given household and month using greedy net-balance matching algorithm. All amounts in cents to avoid floating-point errors.

POST/api/settlements/archiveauth

Archive a month's settlement. Creates Settlement and SettlementTransaction records, marks month as archived. Only admin can archive.

PATCH/api/settlements/transactions/[id]auth

Mark a settlement transaction as settled or unsettled. Any household member can toggle.

POST/api/recurring-templatesauth

Create a recurring expense template with split configuration.

PUT/api/recurring-templates/[id]auth

Update a recurring expense template.

DELETE/api/recurring-templates/[id]auth

Deactivate a recurring template (soft-delete by setting is_active=false).

POST/api/recurring/generateauth

Edge Function cron handler: auto-creates expenses from active recurring templates whose day_of_month matches today. Called daily by Supabase cron.

POST/api/export/csvauth

Generate CSV export of a household's monthly expenses. Returns downloadable CSV file.

POST/api/export/pdfauth

Generate PDF summary of a household's monthly expenses and settlement. Returns downloadable PDF.

PUT/api/members/[id]auth

Update member profile: display name, avatar, payment handles, notification prefs.

POST/api/receipts/uploadauth

Upload a receipt image to Supabase Storage. Returns the public URL for the uploaded file.

Authentication

providers:["email-magic-link","google-oauth"]
redirectUrls:["http://localhost:3000/auth/callback","https://roomietab.vercel.app/auth/callback","https://roomietab.app/auth/callback"]
sessionStrategy:Supabase Auth with PKCE flow. Sessions stored in secure HTTP-only cookies via @supabase/ssr. Server components use createServerClient() to read session; client components use createBrowserClient(). Auth callback handled at /auth/callback route that exchanges code for session. Magic link emails include redirect to /auth/callback. Google OAuth configured in Supabase dashboard with redirect URI. Middleware at middleware.ts refreshes session on every request to prevent expiry.

Dependencies

PackageVersionPurpose
next15.1React framework with App Router, Server Components, and API route handlers
react19.0UI library for building component-based interfaces
react-dom19.0React DOM rendering
@supabase/supabase-js2.47Supabase JavaScript client for database queries, auth, realtime, and storage
@supabase/ssr0.5Supabase server-side rendering helpers for Next.js (cookie-based session management)
react-hook-form7.54Performant form state management with minimal re-renders — ideal for the expense entry form
@hookform/resolvers3.9Connects Zod validation schemas to react-hook-form
zod3.24TypeScript-first schema validation for forms and API request bodies
lucide-react0.468Tree-shakeable icon library with 1500+ icons — used for nav, buttons, category icons
date-fns4.1Lightweight modular date utility for formatting expense dates and month navigation
sonner1.7Minimal toast notification library with built-in action buttons (for Undo on delete)
tailwindcss3.4Utility-first CSS framework for all styling per the design system
@tailwindcss/forms0.5Tailwind plugin for consistent form element styling
clsx2.1Tiny utility for conditionally joining CSS class names
tailwind-merge2.6Merge Tailwind classes without conflicts — used in cn() utility
@react-pdf/renderer4.1Server-side PDF generation for monthly expense export
typescript5.7TypeScript compiler for type safety across the codebase
@types/react19.0TypeScript type definitions for React
@types/node22.0TypeScript type definitions for Node.js
eslint9.0JavaScript/TypeScript linter
eslint-config-next15.1ESLint configuration preset for Next.js projects
prettier3.4Code formatter for consistent style
supabase2.0Supabase CLI for local development, migrations, and type generation