A full-stack personal finance platform with AI-powered document analysis, budget tracking, savings goals, and real-time spending analytics.
Most people struggle with three things when managing personal finances:
- No visibility β they don't know where their money is going month to month
- Unread documents β bank statements, invoices, and contracts pile up unanalyzed
- No accountability β setting a budget mentally never sticks without a system
Finapple solves all three. It gives you a live wallet with categorized spending, a budget manager that tracks itself from your transactions, and an AI that reads your financial documents so you don't have to.
| Service | URL |
|---|---|
| Frontend | https://finapple.netlify.app |
| Backend API | https://finapple-0517.onrender.com |
| Technology | Purpose |
|---|---|
| NestJS | Modular Node.js framework for the REST API |
| Drizzle ORM | Type-safe SQL query builder |
| PostgreSQL (Neon) | Serverless cloud database |
| Cloudinary | File upload and storage (PDFs, images) |
| Mistral AI | AI document analysis (two models) |
| JWT + bcrypt | Authentication and password security |
| Passport.js | JWT strategy middleware |
| Technology | Purpose |
|---|---|
| React 19 | UI framework |
| TypeScript | Type safety across the entire codebase |
| Zustand | Lightweight global state management |
| Recharts | Data visualization (area, bar, pie charts) |
| Tailwind CSS v4 | Utility-first styling |
| shadcn/ui | Component library (sidebar, toasts) |
| Axios | HTTP client with interceptors |
| React Router v7 | Client-side routing with protected routes |
| Vite | Build tool |
- Register and login with email + password
- Passwords hashed with bcrypt (10 salt rounds)
- JWT tokens stored in localStorage via Zustand persist
- Auto token attachment via Axios request interceptor
- Auto logout + redirect on 401 response
- Profile name update and password change from Settings
- Deposit money into your account
- Withdraw with expense category tagging (Food, Rent, Transport, etc.)
- Transfer money to other users by email
- Full transaction history with running balance after each transaction
- Category badges on each transaction row
- Total money-in / money-out summary cards
- Set monthly spending limits per expense category
- Budget spending is tracked automatically β when you make a withdrawal with category "Food", the Food budget progress updates in real time
- Visual progress bars per category:
- π’ Green β on track (< 80%)
- π‘ Yellow β near limit (β₯ 80%)
- π΄ Red β over budget (β₯ 100%)
- Summary cards: total budgeted, total spent, remaining
- Month picker to view past months
- Auto-refresh on window focus + manual refresh button
- Create goals with a name, target amount, and optional deadline
- Fund goals incrementally β add any amount at any time
- Progress bar shows percentage complete
- Auto-marks goal as completed when target is reached π
- Separate sections for active and completed goals
- Daily Cash Flow β area chart showing income vs expense by day
- Spending by Category β pie chart with color-coded segments
- Category Breakdown β sorted list with percentage bars
- 6-Month Overview β grouped bar chart (income, expense, savings per month)
- Month picker to explore any past period
- All charts built with Recharts
- Upload PDFs and images securely to Cloudinary
- View all uploaded files with type badges
- Delete files (removes from both DB and Cloudinary)
- Each file can be sent to AI for analysis
The most distinctive feature. Built on Mistral AI with two different models depending on file type.
User clicks "Analyze" on a Vault file
β
Backend checks if result is cached in DB
β (cache miss)
File type check:
image β pixtral-12b-2409 (vision model)
PDF β mistral-small-latest (text model)
β
Structured JSON extracted from document
β
Result saved to analysis_results table
β
Returned to frontend (future calls use cache)
| File Type | Model | Why |
|---|---|---|
| Images (receipts, scans) | pixtral-12b-2409 |
Multimodal vision β actually sees the image |
| PDFs, other docs | mistral-small-latest |
Text-based, faster and more cost-efficient |
{
"documentType": "Invoice",
"summary": "Invoice from ABC Corp for βΉ45,000 due on 15 June 2025...",
"entities": ["ABC Corp", "HDFC Bank", "ACC-001234"],
"dates": ["15 June 2025", "1 May 2025"],
"amounts": ["βΉ45,000", "βΉ4,050 GST"],
"keyTerms": ["Net 30", "Late payment penalty 2%"],
"riskFlags": ["Overdue by 10 days"],
"confidence": 0.91
}Results are persisted in PostgreSQL. If the same file is analyzed again, the cached result is returned instantly β Mistral is never called twice for the same document. This saves API cost and eliminates wait time on repeat views.
A custom system prompt enforces strict JSON output with no markdown or extra text. The response parser strips any code blocks if the model wraps the output anyway, then falls back to raw text if JSON parsing fails entirely β so the feature never crashes.
Finapple/
βββ finabackend/ # NestJS backend
β βββ src/
β β βββ auth/ # Register, login, JWT, profile
β β βββ wallet/ # Deposit, withdraw, transfer, stats
β β βββ budget/ # Monthly budget limits + spending calc
β β βββ savings/ # Savings goals CRUD
β β βββ vault/ # File upload/delete via Cloudinary
β β βββ analysis/ # Mistral AI document analysis
β β βββ cloudinary/ # Cloudinary provider + service
β β βββ db/ # Drizzle DB module + schema
β β βββ stratgies/ # JWT Passport strategy
β βββ drizzle/ # SQL migration files
β
βββ frontend/ # React + Vite frontend
βββ src/
βββ pages/
β βββ Dashboard.tsx # Overview with live stats
β βββ Wallet.tsx # Wallet with deposit/withdraw/transfer
β βββ Analytics.tsx # Charts and spending insights
β βββ Budget.tsx # Budget manager
β βββ Savings.tsx # Savings goals
β βββ Vault.tsx # File manager + AI analysis
β βββ Settings.tsx # Profile + password
βββ Store/
β βββ authStore.ts # Auth state (Zustand + persist)
β βββ walletStore.ts # Balance + transactions
β βββ budgetStore.ts # Budgets (refreshes after withdrawals)
β βββ savingsStore.ts # Goals state
β βββ analyticsStore.ts # Monthly stats + trend
β βββ FilesDataStore.ts # Vault files
βββ constants/
β βββ categories.ts # Single source of truth for all categories
βββ api/
βββ api.ts # Axios instance + interceptors
users β id, fullName, email, password, balance
transactions β id, userId, type, amount, balanceAfter, category, description
files β id, userId, publicId, url, resource_type
analysis_results β id, fileId, userId, documentType, summary, entities...
budgets β id, userId, category, limitAmount, month
savings_goals β id, userId, name, targetAmount, savedAmount, deadline
Why Drizzle over Prisma? Drizzle is lighter, has no code generation step, and gives full SQL control while keeping TypeScript type safety. Better fit for a project that needs fine-grained date range queries.
Why Zustand over Redux?
Zero boilerplate, no Provider wrapping, and direct getState() access between stores. The wallet store calls budgetStore.getState().fetchBudgets() after a withdrawal β this cross-store communication is trivial in Zustand.
Why two AI models?
A vision model on a PDF is wasteful β it adds latency and cost for no gain. pixtral-12b is used only when the file is actually an image. PDFs get mistral-small which is 5x faster.
Why cache AI results in the DB? Mistral calls can take 3-8 seconds. Showing a cached result instantly on re-visit is a much better UX, and it keeps API costs under control.
Category system as a shared constant
Both Wallet withdrawals and Budget Manager use EXPENSE_CATEGORIES from a single src/constants/categories.ts file. This is the only way to guarantee they always match β the budget spending calculation on the backend matches exactly because the same string values are sent.
DATABASE_URL=postgresql://...
JWT_SECRET=your_secret
CLOUDINARY_CLOUD_NAME=...
CLOUDINARY_API_KEY=...
CLOUDINARY_API_SECRET=...
MISTRAL_API_KEY=...
CORS_ORIGIN=https://your-frontend.netlify.appVITE_API_URL=https://your-backend.onrender.com# Backend
cd Finapple/finabackend
npm install
npm run start:dev
# Frontend
cd Finapple/frontend
npm install
npm run dev| Service | Platform | Config |
|---|---|---|
| Backend | Render | render.yaml in repo root |
| Frontend | Netlify | netlify.toml in frontend folder |
Render settings:
- Root Directory:
Finapple/finabackend - Build:
npm install && npm run build - Start:
npm run start:prod
Built with NestJS Β· Drizzle Β· Neon Β· Cloudinary Β· Mistral AI Β· React Β· Zustand Β· Recharts