Cloud money tracker monorepo: Expo mobile + Next.js (auth, Postgres sync API, desktop web UI) + shared domain package.
apps/mobile Expo Router iOS/Android client (offline-first)
apps/web Next.js App Router — API + desktop UI
packages/domain Shared types, money effects, dates, currency, theme
packages/api-client Typed sync/migrate client
- Node 20+
- Neon Postgres (free tier) — set
DATABASE_URL - Optional: Google Cloud OAuth client (free) for Google sign-in
npm install
cp apps/web/.env.example apps/web/.env.local
# edit DATABASE_URL, BETTER_AUTH_SECRET, BETTER_AUTH_URL, NEXT_PUBLIC_APP_URL
# optional: GEMINI_API_KEY for Ask AI parse + spending Q&A (Google AI Studio)
npm run db:push # push Drizzle schema to Neon
npm run web # Next.js on :3000
npm run mobile # Expo (set EXPO_PUBLIC_API_URL=http://localhost:3000)For a physical device, point EXPO_PUBLIC_API_URL at your machine LAN IP or deployed Vercel URL.
- Email/password via Better Auth (self-hosted in Next)
- Google OAuth when
GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRETare set - Mobile uses
@better-auth/expo+ SecureStore
- Mobile keeps Zustand + AsyncStorage offline cache
- Mutations enqueue ops →
POST /api/sync/push - Pull on focus/network:
GET /api/sync/pull?since= - First cloud login prompts to upload local
ultrack-money-v4/v5data viaPOST /api/migrate
- Web/API: Vercel project rooted at
apps/web(or monorepo withapps/webas root) - Mobile: EAS Build with
EXPO_PUBLIC_API_URLpointing at production
The previous Expo static web export is no longer the product web surface — use Next.js.
Two server-side Gemini features share GEMINI_API_KEY (AI Studio, free Flash tier). The key never goes to web or Expo.
- Parse (step 1) — Add Transaction turns a sentence into a draft.
POST /api/transactions/parse. Nothing is written until the user confirms. Works signed out (local accounts) or signed in. - Spending Q&A (step 2) —
/askanswers questions from the synced cloud ledger only.POST /api/spending/askrequires sign-in. Read-only: plan → real DB aggregates → short answer + evidence. “Add a coffee” hands off to parse; delete/edit is refused.
- Model:
gemini-3.5-flash, then automatic fallback on 429 (3.5-flash-lite→3.1-flash-lite→ other Flash ids). Override start withGEMINI_MODEL. - If the key is missing, the UI shows a clear error instead of crashing
- App rate limits (before Gemini): parse 8/min · 40/hour; ask 5/min · 20/hour · 40/day per signed-in user (Ask is keyed by user id)
| Command | Description |
|---|---|
npm run mobile |
Expo start |
npm run web |
Next.js dev |
npm run db:push |
Drizzle push |
npm run web:build |
Production Next build |