A full-stack transaction management application with passkey authentication, calculator UI, transaction management, multi-user workspaces, budgets, and advanced collaboration features.
- Passkey Authentication: Secure login using Pocket ID (OIDC) with passkeys
- Modern Calculator UI: Calculator with history list, number pad, and operations
- Transaction Management: Create, edit, and manage transactions with account balance tracking
- Account Management: Multiple accounts with balance tracking and charts
- Category & Payee Management: Organize transactions with categories and payees
- Reports: Generate comprehensive reports filtered by account, date, category, payee, and workspace members
- PWA Support: Progressive Web App with offline support and mobile features
- Theme System: Automatic dark/light theme based on time (Catppuccin dark / GitHub light)
- Multi-User Workspaces: Create and manage workspaces for team collaboration
- Role-Based Access Control: Owner, Admin, and Member roles with appropriate permissions
- Workspace Invitations: Invite users via email with role assignment
- Real-Time Updates: GraphQL subscriptions for live updates across all entities
- Conflict Resolution: Automatic conflict detection and resolution for concurrent edits
- Version History: Track entity changes with full version history
- Budget Tracking: Set budgets for accounts, categories, or payees
- Budget Notifications: Get notified when budgets approach or exceed limits
- Automatic Budget Reset: Scheduled budget resets with configurable periods
- Budget Analytics: Track spending against budgets with visual indicators
- PDF Import: Upload credit card statements and auto-match transactions
- CSV Import: Import accounts, categories, payees, and transactions from CSV files
- Auto-Matching: Intelligent transaction matching with import match rules
- Data Export: Export all data (accounts, transactions, budgets, etc.) for backup or migration
- Import Match Rules: Create rules to automatically match imported transactions
- Recurring Transactions: Schedule recurring transactions with cron expressions
- Automatic Balance Reconciliation: Scheduled balance checks and corrections
- Data Archival: Automatic archival of old transactions
- Scheduled Backups: Automated database backups
- Batch Operations: Bulk create/update accounts, categories, payees, and transactions
- Smart Suggestions: AI-powered suggestions for account, category, and payee based on transaction amount
- Transaction Search: Full-text search across transaction notes
- Cursor-Based Pagination: Efficient pagination for large transaction lists
- Query Caching: Intelligent caching for improved performance
- Rate Limiting: Protection against abuse with configurable rate limits
- Framework: React 19 + TypeScript 5.9.3
- Build Tool: Webpack 5
- GraphQL Client: Apollo Client 4 with subscriptions support
- Routing: React Router 7
- UI Framework: Material-UI (MUI) v7 with custom wrapper components
- Charts: Recharts 2
- Form Handling: React Hook Form + Zod validation
- State Management: Apollo Client cache + React Context
- PWA: Service Worker with offline support
- HTTP Framework: Hono 4 (lightweight, fast web framework)
- GraphQL Server: Apollo Server 4 integrated with Hono
- Runtime: Node.js 25.2.1 with ESM modules
- Language: TypeScript 5.9.3
- Database ORM: Prisma 7 with PostgreSQL adapter
- Database: PostgreSQL 18
- Authentication: Pocket ID (OIDC) with passkeys via openid-client
- Real-Time: GraphQL Subscriptions with WebSocket (graphql-ws)
- File Processing: PDF.js for PDF parsing, Busboy for multipart uploads
- Scheduling: node-cron for recurring tasks
- Security: Helmet, CORS, rate limiting, CSRF protection
- Package Manager: npm workspaces (monorepo)
- Containerization: Docker Compose
- Testing: Jest + React Testing Library (TDD methodology)
- Code Quality: ESLint (Google style guide) + Prettier
- Node.js >= 25.2.1 (use
.nvmrcfile withnvm useto ensure correct version) - npm >= 10.0.0
- Docker and Docker Compose (v2:
docker composeCLI) - PostgreSQL 18 (or use Docker)
This project uses .nvmrc to ensure consistent Node.js versions. If you're using nvm:
nvm useThis will automatically switch to Node.js 25.2.1 as specified in .nvmrc.
git clone <repository-url>
cd my-moneyCreate .env files. For Docker you need the root .env; for local development create .env in root, backend/, and frontend/:
# Root (required for Docker Compose)
cp .env.example .env
# Backend and frontend (required when running apps without Docker)
cp backend/.env.example backend/.env
cp frontend/.env.example frontend/.envEdit each .env with your configuration (see .env.example in each directory for variables).
npm run docker:upThis will start:
- PostgreSQL database on port 5432
- Backend GraphQL server on port 4000
- Frontend web app on port 3000
The application uses a hybrid approach:
- Automatic schema sync (via
prisma db push) runs on startup whenRUN_MIGRATIONS=trueor in production - Manual migrations are used for data migrations and production deployments
For Docker setup:
The schema will automatically sync when the backend starts (if RUN_MIGRATIONS=true or NODE_ENV=production).
To manually run migrations instead:
docker exec -it my-money-backend npm run prisma:migrateNote: If you see migration drift errors, see the Migration Guide for resolution steps.
- Frontend: http://localhost:3000
- Backend GraphQL: http://localhost:4000/graphql
npm installThe project includes a Docker Compose configuration for PostgreSQL. This is the recommended approach for local development.
First, ensure your .env file in the project root has the PostgreSQL configuration:
# PostgreSQL Configuration
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
POSTGRES_DB=mymoney
# Database Connection URL
# For local development (connecting from host machine):
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/mymoney
# For Docker containers (connecting from backend service):
# DATABASE_URL=postgresql://postgres:postgres@postgres:5432/mymoneyStart only the PostgreSQL service:
docker compose -f docker/docker-compose.yml --env-file .env up -d postgresImportant: The --env-file .env flag is required when using -f docker/docker-compose.yml because Docker Compose needs to read the .env file from the project root for variable substitution (e.g., ${POSTGRES_USER}) in the compose file. The docker-compose.yml also has env_file: - ../.env which loads variables into the container at runtime.
If you see warnings about missing environment variables:
The container may have been created before the .env file was configured. Recreate the container:
# Stop and remove the container (data is preserved in volume)
docker compose -f docker/docker-compose.yml --env-file .env down postgres
# Start again with the updated .env file
docker compose -f docker/docker-compose.yml --env-file .env up -d postgresConnect to the database:
# Using docker exec (include --env-file to avoid warnings)
docker compose -f docker/docker-compose.yml --env-file .env exec postgres psql -U postgres -d mymoney
# Or using psql from your host (if installed)
psql postgresql://postgres:postgres@localhost:5432/mymoneyNote: Even though docker-compose exec uses the container's existing environment, it still parses the compose file and tries to substitute variables (like ${POSTGRES_USER}), so you need to include --env-file .env to avoid warnings.
# Stop the container
docker compose -f docker/docker-compose.yml --env-file .env stop postgresTo stop and remove the container (data is preserved in volume):
docker compose -f docker/docker-compose.yml --env-file .env down postgresTo remove container and data volume (
docker compose -f docker/docker-compose.yml --env-file .env down -v postgresNote: Include --env-file .env in all docker compose commands that use -f docker/docker-compose.yml to avoid warnings about missing environment variables.
If you prefer to install PostgreSQL locally instead of using Docker:
macOS (using Homebrew):
brew install postgresql@18
brew services start postgresql@18
createdb -U postgres mymoneyLinux (Ubuntu/Debian):
sudo apt update
sudo apt install postgresql-18 postgresql-contrib-18
sudo systemctl start postgresql
createdb -U postgres mymoneyWindows: Download from PostgreSQL official website or use Chocolatey:
choco install postgresql18
createdb -U postgres mymoneyThen update your DATABASE_URL in .env to point to your local PostgreSQL instance.
Create .env in the project root, backend/, and frontend/ so each app has its own config when run directly:
# From project root
cp .env.example .env
cp backend/.env.example backend/.env
cp frontend/.env.example frontend/.envEdit each file with your values. Keep these in sync where they overlap:
- Root & backend:
DATABASE_URL,OPENID_*,CORS_ORIGIN,BACKEND_URL,FRONTEND_URL - Root & frontend:
REACT_APP_GRAPHQL_URL,REACT_APP_OPENID_* - Backend & frontend: OIDC client ID and discovery URL must match
Example for local development: in backend/.env set DATABASE_URL=postgresql://postgres:postgres@localhost:5432/mymoney (host localhost when DB runs in Docker); in frontend/.env set REACT_APP_GRAPHQL_URL=http://localhost:4000/graphql.
The application uses a hybrid approach:
- Automatic schema sync (via
prisma db push) runs on startup whenRUN_MIGRATIONS=trueor in production - Manual migrations are used for data migrations and production deployments
For first-time setup:
cd backend
npm run prisma:generate
# Option 1: Use automatic schema sync (recommended for development)
# Set RUN_MIGRATIONS=true in .env or let it run automatically in production
# The schema will sync on application startup
# Option 2: Use manual migrations (recommended for production)
npm run prisma:migrateNote: If you see migration drift errors, see the Migration Guide for resolution steps.
In separate terminals:
# Terminal 1: Backend
cd backend
npm run dev
# Terminal 2: Frontend
cd frontend
npm run devmy-money/
├── frontend/ # React 19 application
│ ├── src/
│ │ ├── components/ # React components
│ │ │ ├── ui/ # MUI wrapper components
│ │ │ ├── calculator/ # Calculator components
│ │ │ ├── report/ # Report components
│ │ │ ├── budget/ # Budget components
│ │ │ ├── import/ # Import components
│ │ │ └── common/ # Shared components
│ │ ├── pages/ # Page components
│ │ ├── hooks/ # Custom React hooks
│ │ ├── utils/ # Utility functions
│ │ ├── graphql/ # GraphQL operations
│ │ │ ├── queries.ts
│ │ │ ├── mutations.ts
│ │ │ ├── subscriptions.ts
│ │ │ └── workspaceOperations.ts
│ │ ├── contexts/ # React contexts
│ │ ├── theme/ # Theme configuration
│ │ ├── service-worker/ # PWA service worker
│ │ └── docs/ # Frontend documentation
│ ├── __tests__/ # Frontend tests
│ └── public/ # Static assets
├── backend/ # Hono + Apollo GraphQL server
│ ├── src/
│ │ ├── resolvers/ # GraphQL resolvers
│ │ │ ├── transaction/ # Transaction resolvers
│ │ │ └── ...
│ │ ├── schema/ # GraphQL schema
│ │ ├── services/ # Business logic services
│ │ ├── repositories/ # Data access layer
│ │ ├── commands/ # Command pattern implementations
│ │ ├── queries/ # Query pattern implementations
│ │ ├── events/ # Event system
│ │ ├── jobs/ # Job queue
│ │ ├── routes/ # HTTP routes (auth, health)
│ │ ├── config/ # Configuration (Apollo, security, etc.)
│ │ ├── middleware/ # Auth, validation, caching middleware
│ │ ├── utils/ # Utility functions
│ │ ├── cron/ # Scheduled jobs
│ │ │ ├── recurringTransactions.ts
│ │ │ ├── budgetReset.ts
│ │ │ ├── balanceReconciliation.ts
│ │ │ ├── backup.ts
│ │ │ └── dataArchival.ts
│ │ └── docs/ # Backend documentation
│ ├── __tests__/ # Backend tests
│ └── prisma/ # Prisma schema and migrations
├── shared/ # Shared types and utilities
│ └── src/
│ ├── types/ # Shared TypeScript types
│ └── utils/ # Shared utility functions
├── docker/ # Docker configuration
│ ├── docker-compose.yml
│ └── ...
├── scripts/ # Utility scripts
│ ├── kill-ports.ts
│ ├── docker-prepare-images.sh
│ └── docker-prune.sh
├── docs/ # Documentation
│ ├── USAGE.md
│ ├── DEPLOYMENT.md
│ ├── MONITORING.md
│ └── TROUBLESHOOTING.md
└── README.md # This file
npm run dev- Start both frontend and backend in development mode (automatically kills processes on ports 3000 and 4000)npm run kill-ports- Kill processes using ports 3000 and 4000 (or specify custom ports:tsx scripts/kill-ports.ts 3000 4000)npm run build- Build both frontend and backend for productionnpm run test- Run all tests across all workspacesnpm run test:frontend- Run frontend tests onlynpm run test:backend- Run backend tests onlynpm run test:coverage- Generate test coverage reportsnpm run lint- Lint all packagesnpm run lint:fix- Fix linting issues automaticallynpm run type-check- Type check all packagesnpm run format- Format code with Prettiernpm run format:check- Check code formattingnpm run validate- Run lint, type-check, and format checknpm run clean- Remove build artifacts and cachesnpm run docker:up- Start Docker servicesnpm run docker:down- Stop Docker servicesnpm run docker:build- Build Docker imagesnpm run docker:logs- View Docker logsnpm run docker:clean- Stop and remove Docker containers and volumesnpm run docker:prune- Remove unused Docker resources (containers, images, networks, volumes, build cache)
npm run dev- Start development server with hot reloadnpm run build- Build for production (includes service worker)npm run build:sw- Build service worker separatelynpm run build:analyze- Analyze bundle sizenpm run test- Run testsnpm run test:watch- Run tests in watch modenpm run test:coverage- Generate test coveragenpm run type-check- Type check TypeScript filesnpm run lint- Lint source filesnpm run lint:fix- Fix linting issues
npm run dev- Start development server with hot reload (Node.js --watch)npm run build- Build for production (TypeScript compilation)npm run start- Start production servernpm run start:prod- Start production server with NODE_ENV=productionnpm run test- Run testsnpm run test:watch- Run tests in watch modenpm run test:coverage- Generate test coveragenpm run type-check- Type check TypeScript filesnpm run prisma:generate- Generate Prisma clientnpm run prisma:migrate- Run database migrations (development)npm run prisma:deploy- Deploy migrations (production)npm run prisma:reset- Reset database (⚠️ deletes all data)npm run prisma:studio- Open Prisma Studio (database GUI)npm run backup- Create database backup
The project uses Test-Driven Development (TDD) methodology. Tests are written first, then implementation follows.
# All tests
npm run test
# Frontend only
npm run test:frontend
# Backend only
npm run test:backend
# With coverage
npm run test:coverageSee .env.example (root), backend/.env.example, and frontend/.env.example for all available variables. Create .env in each of those directories when running backend or frontend outside Docker (see Configure environment variables for examples).
Additional documentation is available in the docs/ directory:
- User Guide - Complete user guide with instructions for using the application UI, managing accounts, categories, payees, budgets, and more
- Deployment Guide - Step-by-step production deployment instructions
- Monitoring Guide - Monitoring and alerting setup
- Troubleshooting Guide - Common issues and solutions
- Workspaces: Multi-user collaboration with role-based access control
- Version Control: Optimistic locking with version numbers to prevent conflicts
- Conflict Resolution: Automatic detection and manual resolution of concurrent edits
- Real-Time Updates: GraphQL subscriptions for live collaboration
- Batch Operations: Efficient bulk operations for data import/export
- Import Matching: Intelligent auto-matching of imported transactions using rules
GraphQL API is available at /graphql endpoint. The API supports:
- Queries: Read data (accounts, transactions, budgets, workspaces, etc.)
- Mutations: Modify data (create, update, delete operations)
- Subscriptions: Real-time updates via WebSocket
- File Uploads: Multipart file uploads for PDF/CSV imports
query GetAccounts {
accounts {
id
name
balance
accountType
workspaceId
}
}mutation CreateTransaction($input: CreateTransactionInput!) {
createTransaction(input: $input) {
id
value
date
account {
id
name
}
category {
id
name
}
}
}subscription OnTransactionUpdated($workspaceId: ID!) {
transactionUpdated(workspaceId: $workspaceId) {
id
value
date
account {
name
}
}
}The API supports batch operations for efficient bulk updates:
mutation BulkCreateTransactions($inputs: [BatchCreateTransactionInput!]!) {
bulkCreateTransactions(inputs: $inputs) {
created {
id
value
}
errors {
index
message
}
}
}For complete API documentation, explore the GraphQL schema using GraphQL Playground or any GraphQL client.
npm run buildnpm run docker:build
npm run docker:up- Ensure PostgreSQL is running
- Check
DATABASE_URLin.env - Verify database exists and user has permissions
Migration drift errors:
- Common causes: Database created with
db pushbut migrations exist, or empty migration directories - Solution: Use
prisma migrate resetto reset database, or manually resolve drift
P3015 Error (Missing migration file):
- Check for empty migration directories:
ls -la backend/prisma/migrations/ - Remove empty directories and re-run migration
Migration fails because tables don't exist:
- Ensure base schema is applied before data migrations
- Run migrations in order:
npm run prisma:migrate
- Verify Pocket ID is running and accessible
- Check
OPENID_DISCOVERY_URLis correct - Ensure
OPENID_CLIENT_IDandOPENID_CLIENT_SECRETare set
- The
npm run devcommand automatically kills processes on ports 3000 and 4000 before starting - To manually kill ports:
npm run kill-portsortsx scripts/kill-ports.ts [port1] [port2] ... - Change ports in
docker-compose.ymlif needed - Update
REACT_APP_GRAPHQL_URLif backend port changes
- Follow TDD methodology - Write tests first, then implementation
- Code Quality - Use ESLint and Prettier for code formatting
- Documentation - Write JSDoc comments for all functions and classes
- Style Guide - Follow Google TypeScript style guide
- Principles - Follow SRP, KISS, DRY, and functional programming principles
- Security - Keep security in mind, validate and sanitize all inputs
- Performance - Optimize for performance, use caching where appropriate
- Monorepo Structure: npm workspaces for shared code and dependencies
- Type Safety: Full TypeScript coverage across frontend and backend
- GraphQL-First: Single API endpoint with type-safe queries and mutations
- Real-Time Collaboration: WebSocket subscriptions for live updates
- Optimistic Concurrency: Version-based conflict detection and resolution
- Repository Pattern: Clean separation of data access and business logic
- Command/Query Separation: CQRS-inspired patterns for complex operations
- Event-Driven: Event system for decoupled components
- Security-First: Rate limiting, CSRF protection, input sanitization, SQL injection prevention
[Add your license here]