Skip to content
Nafiz Iqbal
Projects

AI expense tracker for Android and web

ThoughtExpense

Type what you spent the way you would say it. “coffee 120, uber 250” becomes logged, categorised expenses, and the categoriser learns from every correction.

Ownership
Personal work
Organisation
Personal product
My role
Sole engineer and designer
Status
Live on Android and web
  • TypeScript
  • React Native
  • Next.js
  • Node.js
  • PostgreSQL
  • Redis
  • Gemini API
ThoughtExpense quick-log input beside two phone screens of the Android app

Overview

ThoughtExpense is a personal expense tracker for people who have given up on expense trackers. The whole product is built around one interaction: a single text input where you type what you spent in plain words.

If capture costs nothing, everything downstream becomes possible: budgets, trends, anomaly alerts, and advice grounded in real data. The thesis was that the input, not the dashboard, decides whether the product survives.

Role and ownership

What I personally did

  • Product definition and interface design.
  • React Native Android app and Next.js web app.
  • Node.js API, PostgreSQL schema, Redis caching, and background jobs.
  • AI parsing pipeline, Google Play billing, and production deployment.

Constraints

  • Solo build across three codebases: Android app, web app, and API.
  • A language model sits in the critical path, and model APIs are slow, rate-limited, and occasionally wrong.
  • Many target users are on mobile networks behind carrier-grade NAT, where thousands of people share one public IP.
  • AI calls cost money per request, so a free tier has to be cheap to run.

Solution

Both clients run a small on-device parser and keyword categoriser, so item counts and category chips appear while you type, before any network call. Composing works offline; only saving needs the server.

On save, the API parses the text with the model, applies a hand-authored category catalogue that overrides the model when it matches, and returns low-confidence items for review instead of silently saving them. Corrections feed a learning loop so categorisation becomes more consistent over time.

Heavy endpoints have an async variant backed by a job queue, so a phone never has to hold a long connection open on a weak network.

Try the pre-parser

ThoughtExpense live demo

  • Parsed items appear here as you type.

0 items, ৳0

A simplified version of the on-device parser. It runs in your browser; nothing you type is sent or saved.

  1. Clients

    • Android app

      React Native, on-device pre-parse

    • Web app

      Next.js, same pre-parse rules

  2. API

    • Node.js REST API

      Auth, per-user rate limits

    • Job queue

      Async variants of heavy endpoints

  3. Parsing

    • Category catalogue

      Overrides the model on a match

    • Model chain

      Retry with backoff, then fallback models

  4. Data

    • PostgreSQL

      Expenses, budgets, corrections

    • Redis

      Cache and limits, fails open

Request path from a typed line to saved, categorised expenses.
Quick Log screen parsing three expenses from a single typed line
Insights screen with smart alerts and a monthly spending summary

Key decisions

  1. Statistics decide, the model narrates

    Insights like “unusual spend this week” must be correct, and a language model will happily invent a number.

    Considered
    Send raw transactions to the model and let it produce insights end to end.
    Chose
    Anomaly detection, trend detection, and large-transaction flags are pure functions over currency-normalised data with documented thresholds. The model only writes the explanation, under rules that forbid figures that cannot be derived from the data.
    Trade-off
    Fewer “creative” insights, and every new insight type needs code. In return, the numbers are testable and the model cannot be wrong about money.
  2. Rate-limit by user, not by IP

    Behind carrier-grade NAT, one public IP can represent a whole neighbourhood of mobile users.

    Considered
    Standard per-IP buckets at the reverse proxy.
    Chose
    Authenticated routes are limited per user ID. Cache failures fail open, so a Redis outage degrades limits instead of breaking requests.
    Trade-off
    Limits live in application code rather than the proxy, and abuse before sign-in needs separate handling.
  3. A fallback chain instead of a single model

    One model provider having a bad minute should not mean a user cannot log lunch.

    Considered
    Call one model and surface errors to the user.
    Chose
    Calls retry with backoff, then cascade through a chain of fallback models. A curated catalogue handles common items without the model at all.
    Trade-off
    More code paths to test and slightly different output styles across models.

Outcome

  • 3

    Production codebases shipped solo

    Android app, web app, and API, roughly 26,000 lines of TypeScript in total.

  • 45+

    REST endpoints

    Counted from the API route definitions.

  • Free and premium tiers with Google Play billing, webhook reconciliation, and an account-deletion flow that meets Play Store policy.

Reflection

Keeping the model out of anything that must be correct made the product far easier to test. What remains hard to test is narration quality, which needs review by reading, not by assertion.

Perceived speed came more from instant on-device feedback than from making the model call faster.

Notes and further reading