WeSplit is a shared-expense tracking app for groups such as trips, households, and clubs. It helps members record expenses, see who owes whom, and settle balances—while enforcing role-based access control (RBAC) within each group.
The app is multi-tenant: a single user can belong to multiple groups, with different roles in each one.
Production deployment: wesplit-app.vercel.app
-
Simplifies shared expense tracking
Centralizes group expenses in one place and automatically calculates who owes whom. -
Reduces reconciliation friction
Clear, up-to-date balances and settlement history reduce confusion and disputes at the end of a trip or billing period. -
Permissioned, auditable collaboration
Role-based permissions prevent accidental edits and support a transparent, auditable history of changes.
- Users authenticate into WeSplit with a personal account.
- A user can belong to zero, one, or many groups.
- A user can hold different roles in different groups.
User-related session handling lives in:
lib/session.ts– helpers for working with the authenticated user/session.middleware.ts– protects authenticated routes (e.g. dashboard) and routes unauthenticated users to auth pages.
- A group represents a shared context where expenses are tracked (e.g. “Spain Trip 2025”, “Apartment Roommates”, “Book Club”).
- Each group has:
- A name and optional metadata.
- A list of members (linked to user accounts).
- A role assignment for each member, defining what they can do.
Group-related state on the client is managed via:
contexts/groupContext.tsx– React context for the currently selected group, list of groups, and group-level operations.
Roles are per group. A user’s role in one group does not affect other groups.
Typical roles:
-
Admin
- Manage group settings and membership.
- Invite/remove members.
- Assign or change roles.
- Add, edit, and delete any expenses.
- Record settlements.
- View all balances and history.
-
Editor
- Add, edit, and delete any expenses in the group.
- Record settlements.
- View balances and history.
- Cannot manage roles or membership.
-
Contributor
- Add new expenses.
- Edit or delete their own expenses (and optionally others’ depending on policy).
- View balances and history.
- Cannot manage roles, membership, or group-wide settings.
-
Viewer
- Read-only access.
- View expenses, balances, and settlements.
- Cannot create, edit, or delete data.
RBAC and validation logic is represented in the server/domain layer through:
lib/types.ts– shared domain types (groups, expenses, roles, etc.).lib/validation.ts– input validation schemas that also encode what fields and shapes are allowed for different operations.
All write operations in the app are expected to check:
- The user is authenticated (via
lib/session.tsandmiddleware.ts). - The user is a member of the target group.
- The user’s role in that group grants permission for the requested action.
- Create and manage groups for different contexts (trips, households, clubs, etc.).
- Invite members to a group and assign roles (Admin, Editor, Contributor, Viewer).
- Switch between multiple groups from the dashboard.
Relevant code areas:
app/dashboard/*– main dashboard experience after login.contexts/groupContext.tsx– group selection and group-level actions.lib/dal.ts/lib/db.ts– server-side operations that persist groups and memberships.
- Add expenses with:
- Amount
- Payer (who paid)
- Participants (who shares the cost)
- Split method (e.g. equal split; optional custom split)
- Date
- Category
- Description/notes
- Automatically split expenses and update everyone’s net position in the group.
- Edit or delete expenses according to the user’s role and ownership rules.
- Keep a chronological list of all group expenses.
Relevant code areas:
contexts/expenseContext.tsx– client-side state and operations for expenses.lib/validation.ts– validating new and updated expense payloads.lib/dal.ts– expense CRUD and balance-affecting operations.
- Compute a net balance per member inside each group:
- Amount they owe vs. amount they are owed.
- Show an easy-to-understand breakdown such as:
- “You owe Alice $20”
- “Bob owes you $15”
- Provide aggregated views of total balances per member to simplify reconciliation.
Relevant code areas:
contexts/expenseContext.tsx– deriving per-member balances and exposing them to the UI.lib/types.ts– defines the shapes of balance and summary objects.lib/utils– balance and formatting helpers (e.g. currency, number formatting).
- Record settlements between members (e.g. one member paying another back).
- Each settlement includes:
- Payer and payee
- Amount
- Date
- Optional note
- Settlement records adjust the balances accordingly.
- Display a settlement history for transparency and auditability.
Settlements reuse the same infrastructure as expenses:
- Types and validation in
lib/types.tsandlib/validation.ts. - State and views under
contexts/expenseContext.tsxandapp/dashboard/*.
- A single user can:
- Join multiple groups.
- Have different roles in each group (e.g. Admin in one group, Viewer in another).
- Data isolation:
- Expenses, balances, and settlements are always scoped to a specific group.
- Users can only see and act on groups where they are a member, with permissions enforced by RBAC and server-side checks.
This is reflected in:
- Shared DAL methods (
lib/dal.ts) that always includeuserIdandgroupIdin queries. - Contexts (
userContext.tsx,groupContext.tsx,expenseContext.tsx) that keep current user and group in sync.
- Critical operations (expenses, settlements, role changes, membership changes) are permission-controlled via RBAC.
- Validation and parsing through
lib/validation.tsensures only well-formed inputs reach the database. - The app uses:
- Centralized error boundaries:
app/error.tsx - A 404 page:
app/not-found.tsx - Loading and suspense handling:
app/loading.tsx
- Centralized error boundaries:
Together, these improve resilience and clarity in the UI when something goes wrong.
- Framework: Next.js (App Router)
app/layout.tsx– root layout and providers.app/page.tsx– public landing page / entry point.app/(auth)– authentication-related routes.app/dashboard– authenticated dashboard and main application views.app/api/*– API routes/route handlers for server-side operations.
- Language: TypeScript
- Styling: Global CSS via
app/globals.cssplus component-level styles. - Data Layer:
lib/db.ts– low-level database client and connection handling.lib/dal.ts– data access layer (groups, users, expenses, settlements).
- Domain / Utilities:
lib/types.ts– shared domain and DTO types.lib/validation.ts– validation schemas for inputs.lib/utils.tsandlib/utils/*– shared utility functions.lib/fetcher.ts– small abstraction aroundfetchfor client-side calls.
- State Management (React Contexts):
contexts/userContext.tsx– user-level state (current user, auth state, etc.).contexts/groupContext.tsx– group selection and group-level operations.contexts/expenseContext.tsx– expenses, balances, and settlements state.
- Auth & Middleware:
lib/session.ts– session helpers (read current user, enforce login).middleware.ts– route protection and request-time auth checks.
- Config & Build:
next.config.ts,tsconfig.json,postcss.config.mjs,components.json.
app/(auth)/– login/registration/auth flows.dashboard/– authenticated area, including:- Group lists and selectors.
- Expense and balance views.
- Settlement flows.
api/– server-side handlers for group/expense/settlement operations.layout.tsx– wraps all pages with shared layout and providers (contexts, theme).page.tsx– marketing or entry landing page.error.tsx,not-found.tsx,loading.tsx– UX around errors and loading states.
components/– shared UI building blocks (forms, tables, modals, layout).contexts/– React contexts encapsulating client-side domain state and operations.lib/– data access, domain logic, validation, and helpers.public/– static assets (icons, images, etc.).docs/– documentation sources;docs/diagramscontains system/architecture diagrams.
The conceptual model aligns with the types defined in lib/types.ts. At a high level:
- Represent authenticated persons using WeSplit.
- Types defined in
lib/types.ts, sessions handled inlib/session.ts.
- Shared context for expenses.
- CRUD and membership handled through DAL methods in
lib/dal.ts.
- Link users to groups and store their role.
- Used to implement multi-tenancy and RBAC decisions in both DAL and API handlers.
Expenseentities represent each expense inside a group.ExpenseParticipantor equivalent defines how each expense is split among members.- Defined in
lib/types.ts, validated vialib/validation.ts, persisted throughlib/dal.ts.
- Represent recorded payments between members that reduce outstanding balances.
- Types and validation live in
lib/types.ts/lib/validation.ts. - Persisted and queried via
lib/dal.ts.
RBAC is enforced at several layers:
-
Middleware
middleware.tsensures that protected routes (e.g./dashboard) are only accessible to authenticated users. -
Session Layer
lib/session.tsexposes helpers to:- Read the active user.
- Enforce login at the server-handler level.
-
Data Access Layer
lib/dal.tsalways works with:userId(from session).groupId(from request/route).
and cross-checks with group membership to ensure the caller is allowed to perform the operation.
-
Validation & Types
lib/validation.tsandlib/types.tsencode rules about what fields can be modified in which operations, helping prevent unsafe mutations.
The logic for balances follows this conceptual flow:
- For each expense:
- Credit the payer with the full
amount. - Debit each participant by their share (equal or custom).
- Credit the payer with the full
- For each settlement:
- Debit the payer (
fromUser) by the settlement amount. - Credit the payee (
toUser) by the same amount.
- Debit the payer (
- Aggregate these into a per-user net balance for each group.
Key places this appears:
contexts/expenseContext.tsx– deriving per-member balances and exposing them to the UI.lib/utils– helpers for numeric calculations and display formatting.
The app uses Next.js App Router app/api/* route handlers for server-side APIs. These:
- Use
lib/session.tsto authenticate the caller. - Delegate reads and writes to
lib/dal.ts. - Validate inputs via
lib/validation.ts. - Rely on
lib/db.tsfor low-level database access.
Common responsibilities:
- Groups
- Create/update groups.
- List groups for the current user.
- Manage memberships and roles.
- Expenses
- CRUD operations for group expenses.
- Returning expense lists with computed splits.
- Settlements
- Record and list settlements per group.
On the client, these APIs are typically called through:
lib/fetcher.ts– a small abstraction for typed/fetch calls from the frontend.- Contexts in
contexts/*.tsx, which coordinate state and server calls.
- Node.js (LTS)
- npm (or another package manager like pnpm / yarn, if you prefer)
From the project root:
npm install
npm run devThen open http://localhost:3000 in your browser.
WeSplit expects environment variables (e.g. in .env.local) to configure:
- Database connection (
DATABASE_URL, etc., used bylib/db.ts). - Auth/session secrets.
- Any third-party services (if configured).
Refer to your deployment or .env.example (if present) for the exact list.
WeSplit is under active development. The structure in app/, contexts/, lib/, and docs/ is designed to support:
- Extending RBAC rules.
- Adding new settlement/expense types.
- Iterating on the UX while keeping core domain logic centralized.