WalletLens is a full-stack personal finance web app for tracking income/expenses, scanning receipts with OCR + AI parsing, building budgets, and viewing reports.
- Authentication with JWT + session tracking
- Google OAuth login/registration option
- Optional email-based 2FA (enable/disable from Settings)
- Home dashboard with KPI cards, category chart, recent transactions, and net-worth snapshot
- Receipt uploads (PDF/images), OCR extraction, AI parsing, and auto-created expense records
- Records management for both expenses and income (search across row content, filter, column-header sort, paginate, export CSV)
- Budgeting by cadence/period with saved budget sheets and custom categories
- Reports with date ranges, category breakdowns, time-series trends (time on X-axis, amount on Y-axis), and currency conversion support
- Profile management (editable profile, avatar picker, activity feed)
- Settings for theme, currency, timezone, dashboard defaults, password change, and account/session controls
- Help/support form wired to backend email delivery
- Legal and help pages are included in the React app routes.
- Frontend: React, Vite, CSS, and JavaScript modules (
web/) - Backend: Node.js 20, Express (
api/) - Database: PostgreSQL
- Object storage: Cloudflare R2 (S3-compatible presigned upload/download)
- OCR worker: Python + Tesseract + PyMuPDF (
worker/) - AI parsing: Google GenAI (
@google/genai)
WalletLens/
web/ # static frontend pages + scripts + styles
api/ # Express API, models, controllers, routes, migrations
worker/ # Python OCR worker script
Dockerfile # container setup for API + OCR runtime
- Node.js >= 20
- Python 3.10+
- PostgreSQL
- Tesseract OCR installed on your machine
# API deps
cd api
npm install
# OCR worker deps
cd ../worker
python3 -m pip install -r requirements.txtCreate api/.env:
NODE_ENV=development
PORT=4000
DB_PROVIDER=postgres
DB_URL=postgresql://USER:PASSWORD@HOST:5432/DBNAME
DB_SSL=false
JWT_SECRET=replace-with-a-strong-secret
JWT_EXPIRES_IN=7d
SESSION_IDLE_DAYS=1
SESSION_CLEANUP_DAYS=30
TWO_FA_CODE_MINUTES=10
TWO_FA_TRUSTED_DAYS=10
GOOGLE_CLIENT_ID=your_google_oauth_client_id
GOOGLE_CLIENT_SECRET=your_google_oauth_client_secret
GOOGLE_REDIRECT_URI=http://localhost:4000/api/auth/google/callback
CORS_ORIGIN=http://localhost:5173,http://127.0.0.1:5173,http://localhost:5500,http://127.0.0.1:5500
OCR_ENABLED=true
OCR_WORKER_SCRIPT=../worker/ocr_demo.py
PYTHON_BIN=python3
RECEIPT_KEEP_FILES=true
AI_PROVIDER=gemini
AI_API_KEY=your_google_ai_key
AI_RECEIPT_API_KEY=your_google_ai_key_for_receipts
AI_TAX_API_KEY=your_google_ai_key_for_tax_sync
AI_MODEL=models/gemma-3-4b-it
AI_CHAT_MODEL=models/gemini-2.5-flash
AI_RECEIPT_MODEL=models/gemini-2.5-flash
AI_MAX_CHARS=5000
OBJECT_STORE_PROVIDER=r2
OBJECT_STORE_BUCKET=your_bucket
OBJECT_STORE_ENDPOINT=https://<accountid>.r2.cloudflarestorage.com
OBJECT_STORE_ACCESS_KEY_ID=your_access_key
OBJECT_STORE_SECRET_ACCESS_KEY=your_secret_key
OBJECT_STORE_REGION=auto
OBJECT_STORE_FORCE_PATH_STYLE=true
# Optional email settings (for support + 2FA emails)
EMAIL_FROM=no-reply@wisewallet.local
SUPPORT_EMAIL=support@example.com
# SMTP_HOST=
# SMTP_PORT=
# SMTP_USER=
# SMTP_PASS=
# SMTP_SECURE=false
# BREVO_API_KEY=
# BREVO_API_URL=https://api.brevo.com/v3/smtp/emailYou can keep one shared Gemini key, but the better setup is separate keys by workload:
AI_API_KEY: WalterLens chat and general AI text featuresAI_RECEIPT_API_KEY: receipt OCR parsing and category extractionAI_TAX_API_KEY: admin tax-rate sync
All dedicated keys are optional. If you leave them unset, the app falls back to AI_API_KEY.
Recommended setup:
- Open Google AI Studio and sign in:
https://aistudio.google.com/apikey
- Create three API keys:
- one for chat/general AI
- one for receipt parsing
- one for tax sync
- Name them clearly so usage is easy to track:
walletlens-chatwalletlens-receiptswalletlens-tax
- Put them in
api/.env:
AI_API_KEY=your_chat_key
AI_RECEIPT_API_KEY=your_receipt_key
AI_TAX_API_KEY=your_tax_key- Restart the API after editing env vars.
Notes:
AI_RECEIPT_API_KEYfalls back toAI_API_KEYif omitted.AI_TAX_API_KEYfalls back toAI_API_KEYif omitted.- You can also use the alternate names
GEMINI_RECEIPT_API_KEYandGEMINI_TAX_API_KEY. - If you are on Gemini free tier, splitting keys helps isolate features, but quota still depends on the limits of each key/project arrangement in your Google setup.
- In Google Cloud Console, create/select a project.
- Configure OAuth consent screen.
- Create an OAuth client of type
Web application. - Add authorized JavaScript origins for your frontend, for example:
http://localhost:5173http://127.0.0.1:5173- your production frontend origin (e.g.
https://yourdomain.com)
- Add authorized redirect URIs, for example:
http://localhost:4000/api/auth/google/callback- your production API callback URL (e.g.
https://api.yourdomain.com/api/auth/google/callback)
- Set
GOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRET, andGOOGLE_REDIRECT_URIinapi/.env.
Note: If your OAuth app is in Google "Testing" mode, only listed test users can sign in. For any Google account to sign in, publish the app to production in Google Cloud.
Run all SQL files in api/src/db/migrations against your Postgres database (in filename order).
Example:
for f in api/src/db/migrations/*.sql; do
psql "$DB_URL" -f "$f"
doneFrom the repo root, start both the API and the Vite React frontend:
npm run dev:allOpen: http://localhost:5173
You can also run the API and frontend separately:
cd api
npm run devAPI health check:
curl http://localhost:4000/healthDatabase readiness check:
curl http://localhost:4000/readynpm run dev:webThe frontend calls the local API at http://localhost:4000/api by default.
/api/auth/api/records/api/receipts/api/budget-sheets/api/fx-rates/api/activity/api/support
Google OAuth routes:
GET /api/auth/google/configGET /api/auth/google/startGET /api/auth/google/callback
cd api
npm run dev # run API with nodemon
npm start # run API with node
npm run worker # run dedicated receipt job worker
npm run migrate # apply SQL migrations in order (tracked)
npm test # run API tests (node:test)
npm run cleanup:sessions # cleanup expired/stale sessions
npm run replace:categories # category replacement utility- Run migrations in every environment before deploy:
cd api && npm run migrate
- Dedicated receipt worker setup (recommended):
- API: set
RUN_RECEIPT_WORKER_IN_API=false - Worker process: run
npm run worker
- API: set
- Optional automatic migrations on startup:
- set
AUTO_RUN_MIGRATIONS=true
- set
- Optional captcha on public support endpoint:
- set
TURNSTILE_SECRET_KEY=<secret> - frontend should send
captchaTokentoPOST /api/support/public
- set
Dockerfileinstalls Node, Python, and Tesseract for OCR-compatible API deployment.- Frontend API base is auto-switched in
web/scripts/api.js:- localhost ->
http://localhost:4000/api - non-localhost -> hosted Render API URL
- override locally with
VITE_API_BASE_URL
- localhost ->