A personal expense tracking web application for managing income, expenses, and transfers between users in a shared household. Built with TypeScript, React, and PostgreSQL.
# Install dependencies
bun install
# Start dev database (Docker)
bun create-dev-db
# Create .env file (see Settings section below)
# Run database migrations
bun migrate
# Add example data (optional)
bun seed
# Start server (in one terminal)
bun server
# Start client dev server (in another terminal)
bun uiSetup autorebase on git:
git config --global pull.rebase trueOld settings, used only for current repository:
git config branch.autosetuprebase always
git config branch.master.rebase trueInstall deps with bun.
The full database schema is documented in docs/SCHEMA.sql.
You can regenerate it with bun dump-schema.
If you want to use a docker DB, start postgres DB with bun create-dev-db.
Note for Windows: if server gives error role "Username" does not exist,
log in to database (for example, with DBeaver), and create the missing role.
Create file .env with the following contents (adjust as required):
Local dev DB is running on port 15488 by default (if installed via Docker
with bun create-dev-db).
SERVER_PORT=3100
LOG_LEVEL=info
SHOW_ERROR_CAUSE=true
SESSION_TIMEOUT=20 minutes
DB_URL=postgresql://postgres:postgres@localhost:15488/postgres
DB_SSL=false
DEBUG=bookkeeper*To send traces and logs to Grafana Cloud,
add the following to .env (values from the Grafana Cloud OTLP setup page):
OTEL_EXPORTER_OTLP_ENDPOINT=https://otlp-gateway-prod-eu-west-2.grafana.net/otlp
OTEL_EXPORTER_OTLP_HEADERS=Authorization=Basic%20<base64>
OTEL_SERVICE_NAME=bookkeeper
OTEL_ENVIRONMENT=development
# Log shipping to Grafana Loki (find these under your Grafana Cloud Loki data source settings)
GRAFANA_LOKI_HOST=https://logs-prod-eu-west-2.grafana.net
GRAFANA_LOKI_USERNAME=123456
GRAFANA_LOKI_PASSWORD=glc_eyJ...When these are omitted, the server runs without any telemetry overhead.
Setup database schema by running bun migrate.
Add example data by running bun seed.
Start server by running bun server.
The DEBUG switch (in .env or supplied as an environment variable) controls logging output.
Start development build by running bun ui.
bun run <target> or just bun <target>:
server: Start server for development use (runsbun --watch)ui: Start client builder for development (runsvite)build-server: Build production version of server underbuild-server/build-client: Build production bundle of web app underbuild/start-server: Runs production serverps-server: Shows the process number of the active serverstop-server: Kills the running server instance (in case the port has not been released)migrate: Run migrations (this is automatically run on dev-server startup)migrate-make migration-name: Create a new migration filerollback: Rollback latest migrationdump-schema: Dump current DB schema todocs/SCHEMA.sql
- Run
bun testto execute tests (uses Bun's built-in test runner) - Integration tests require the dev server to be running
- Run
bun lintto check for lint errors - Run
bun formatto auto-fix formatting issues
This project includes comprehensive documentation for both developers and AI agents:
- Architecture - Detailed codebase structure, technology stack, and coding patterns
- Backend Improvements - Planned backend improvements and tech debt
- Frontend Improvements - Planned frontend improvements and tech debt
These files provide context for AI-assisted coding:
- Backend Rules - Server-side development conventions
- Frontend Rules - React/UI development conventions
- Code Improvement - Code review and best practices persona
- Documentation Rules - Documentation standards and review guidelines
- Credits - Attribution for icons and images
- Source image (bank card): 52 x 34 px = 208 x 136 px @4x
Each expense has a sum stored in DB table expenses.sum, and a corresponding
division as rows in expense_division.
Expense sum is always non-negative.
For each expense_division.expense_id, the sum sum(expense_division.sum) equals 0.
There are three types of expenses: expense, income, and transfer.
expense: user has purchased something. The sumsum(expense.sum)forexpense.type = expensegives the total cost of the registered expenses. Eachexpenseis divided intocosts andbenefits:cost: tracks who has paid for the expensebenefit: tracks who benefits from the purchase
income: user has received income. The sumsum(expense.sum)forexpense.type = incomegives the total income of the registered expenses. Eachincomeis divided intoincomes andsplits:income: tracks who has received the moneysplit: tracks who should benefit from the money
transfer: money is transferred within the group. These expenses do not contribute to total cost or income, but they do affect user balance. Eachtransferis divided intotransferors andtransferees:transferor: tracks who has transferred the moneytransferee: tracks who has received the money
- For each expense with
expense.type = expense:- The sum of division rows with
expense_division.type = costmust equal-expense.sum - The sum of division rows with
expense_division.type = benefitmust equalexpense.sum
- The sum of division rows with
- For each expense with
expense.type = income:- The sum of division rows with
expense_division.type = incomemust equalexpense.sum - The sum of division rows with
expense_division.type = splitmust equal-expense.sum
- The sum of division rows with
- For each expense with
expense.type = transfer:- The sum of division rows with
expense_division.type = transferormust equal-expense.sum - The sum of division rows with
expense_division.type = transfereemust equalexpense.sum
- The sum of division rows with
For user u with id u.id, we define user value as the
sum sum(expense_division.sum) of all division rows
with expense_division.user_id = u.id.
A positive user value means that the user has gained more benefit than losses from the
registered expenses, and a negative value means that the user has paid for more than what
he has benefitted.
Thus, we further define user balance to equal user value negated, so that
user balance means (semantically) what the user's current balance is (in regards to the
registered entries); a positive user balance means that the user is owed money, and
a negative user balance means that the user is behind the budget and should pay for
shared expenses.