Use a Google Sheet as a lightweight database to track daily expenses with category detection, budgeting stats, and a minimal glassmorphic UI. Ideal for personal finance when a full backend feels like overkill.
- Overview
- Features
- Tech Stack
- Architecture & Data Flow
- Quick Start
- Spreadsheet Preparation
- Google Service Account Auth
- Environment Variables
- API Routes
- React Query & Caching
- Fuzzy Category Matching
- Deployment (Netlify)
- Troubleshooting
- Contributing / Extending
- References
This application treats a Google Spreadsheet as the source of truth for:
- Expense categories (names + optional colors)
- Monthly expense rows (category, amount, description)
- Budget summary values
No external database is required; Google Sheets + a service account powers read/write operations via Next.js API routes.
- Add expenses with category, amount, and description
- Auto-fills emoji icon (first emoji extracted from category name)
- Budget overview (necessities vs wants)
- Unverified item workflow (process & delete)
- Category similarity detection using internal Levenshtein implementation
- Glassmorphic card UI (blur + semi-transparent panels)
- React Query caching + explicit invalidation (
unverified,api/budget) - Works entirely with a single Google Sheet document
- Next.js 15 (App/Pages hybrid, API routes for serverless functions)
- React 18 + TypeScript 5
- Chakra UI 2 (component styling + responsive primitives)
- React Query 5 (
@tanstack/react-query) for client-side caching - SWR (legacy usage in some hooks; consider consolidating)
- Google Sheets API via
googleapis+google-auth-library - pnpm workspace tooling
High-level flow:
- User enters expense (amount + description) and selects a category.
- Client calls
POST /api/trackwhich appends a row to the latest (rightmost) month sheet. - React Query invalidates relevant caches to refresh budget + unverified views.
- Budget stats aggregate totals from categorized rows.
Sheet layout expectations:
- Sheet 1:
Data(Column A: category names, Column B: optional color value) - Subsequent sheets: monthly sheets (e.g.
January 2025) with rows[Category, Amount, Description]starting at A/B/C.
If you change structure, adjust sheetRange and parsing logic in src/pages/api/track.ts and related endpoints.
# Prefer pnpm
pnpm install
pnpm dev
# If needed
npm install && npm run dev
yarn install && yarn devVisit http://localhost:3000
- Create a sheet named
Data(must be first / leftmost). - Column A: category names (e.g.
💻 Tech,🍔 Food). - Column B: optional color codes (hex or recognized style value).
- Add monthly sheets manually (e.g.
January 2025,February 2025). The app writes to the last (rightmost) sheet. - Ensure consistent casing/spelling to improve matching quality.
Example structure:
Data | (A: Name) (B: Color)
January 2025 | (A: Category) (B: Amount) (C: Description)
February 2025 | ...
Create a Google Cloud Project → Enable Sheets API → Create Service Account → Generate JSON key.
Minimal steps:
- Open Google Cloud Console → New Project.
- APIs & Services → Enable
Google Sheets API. - Credentials → Create Service Account → Download JSON Key.
- Share your target spreadsheet with the
client_emailfrom the key.
Retain only:
client_emailprivate_key(must preserve line breaks, use quoted string in.env).
Create .env in project root:
CLIENT_EMAIL=your-service-account@project-id.iam.gserviceaccount.com
PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nABC...XYZ=\n-----END PRIVATE KEY-----\n"
SPREADSHEET_ID="<spreadsheet id>"
Notes:
- Spreadsheet ID is the middle segment of the sheet URL.
- Private key must be quoted and newlines encoded as
\n. - Replace escaped newlines at runtime (see
apiServer.tsimplementation).
| Route | Method | Purpose |
|---|---|---|
track.ts |
POST | Append single expense row to active month sheet |
trackbulk.ts |
POST | Append multiple expenses in one request |
trackexternal.ts |
POST | External tracking endpoint variant |
budget.ts |
GET | Aggregate budget + category totals |
data.ts |
GET | Fetch raw sheet category + color metadata |
unverified.ts |
GET/DELETE | Manage unverified expense entries |
Adjust logic here if you change sheet layout or add new computed fields.
Primary query keys:
unverified→ Unverified expense list.api/budget→ Aggregated budget metrics.
Invalidated on:
- Successful expense deletion (
mutation.onSuccess,onSettled). - (Consider invalidating after adding a new expense for live budget refresh.)
Originally relied on a deprecated external string similarity library. Replaced with an internal Levenshtein-based scoring function (misc.ts) to suggest closest category matches. This avoids extra dependencies and deprecation risk.
Recommendations:
- Use Netlify Next.js plugin (
@netlify/plugin-nextjs). - Ensure Node version ≥ 22 (see
package.jsonengines field) or adjust build image. - Do not use static export (
next export) because API routes must remain serverless functions. - Set environment variables via Netlify UI (no surrounding quotes needed except for multi-line private key which should remain escaped).
Build command example:
pnpm build
Publish directory: (handled by Next.js plugin; typically .next)
| Issue | Cause | Fix |
|---|---|---|
| 401 / permission error | Sheet not shared with service account | Share spreadsheet with client_email |
| Private key parse error | Missing quotes or raw newlines | Wrap key in quotes; encode newlines as \n |
| Empty budget view | No monthly sheet or wrong sheet order | Ensure at least one month sheet exists after Data |
| API route 404 on deploy | Static export performed | Use standard next build only |
| NaN progress circle | Division by zero (0 total) | Guard with total > 0 ? ... : 0 |
Ideas:
- Add authentication layer (e.g. NextAuth) for multi-user segregation.
- Bulk import from CSV.
- Persist category emojis separately.
- Add charts for monthly trends.
- Consolidate SWR fully into React Query.
Development tips:
- Keep components small (
TrackSection,TrackerTitle). - Type props interfaces explicitly.
- Invalidate React Query keys after mutations for accurate UI.
- Blog: How to Use Google Sheets As a Database For Your Business
- Google Sheets API Docs
- Next.js Documentation