Building Tally Budget: A Full-Stack Mobile Budgeting App

January 7, 2026

I built a budgeting app from scratch. This is the story of why it exists, the product decisions that shaped it, and the technical architecture that powers it.

If you're curious about how AI coding tools changed my development workflow while building Tally, I wrote about that separately in The Shift: When the Tools Finally Caught Up.

Why I Built Tally Budget

I've always been frustrated with budgeting apps. They force you into rigid calendar-month cycles, bombard you with ads (or are overpriced), require bank account connections (which always break), and disconnect you from your spending habits.

I wanted something different.

  • Custom budget periods - Budget bi-weekly, weekly, or any period that matches your paycheck schedule

  • Manual transaction entry - Stay mindful by logging each expense yourself

  • No bank connections - No more broken syncs or security concerns

  • Household sharing - Budget together as a couple or family with full transparency

  • Clean, modern UI - A beautiful, simple mobile experience

Most budgeting apps treat the calendar month as sacred. But my wife and I like to budget based on our credit card statement cycle. That mismatch was the core problem I wanted to solve.

Starting with the Problem

My wife and I had been using a Google Sheet for our budget. It was simple. Income at the top, categories with allocated amounts, and transactions logged manually. Nothing fancy, but it worked for us. However, I am not great with excel. And like all developers, I'd rather spend hours or days building something new than being inconvenieced by an existing solution.

The problem with the spreadsheet was that it made budgeting feel like a chore. We'd dread the end-of-week ritual. Sit down together, review our credit card statement, manually enter dozens of transactions. Life gets busy, and suddenly you're three weeks behind on logging expenses. We needed a way to capture transactions in the moment, wherever we were, so the data entry didn't pile up into something we'd avoid.

That spreadsheet became my north star. Every feature decision came back to one question: does this truly help us budget and track our spending habits?

The Stack Decisions

Before getting started, I had to specify at least the basics of my intented tech stack. Based off of my experience, the choices were simple.

  • Supabase for Backend-as-a-Service
  • React Native with Expo and TypeScript
  • Legend State for state management

Why Supabase

Supabase was the obvious choice for a Backend-as-a-Service. It eliminated the need to build APIs from scratch, manage database connections, or roll out a custom auth solution. Out of the box, I got everything I needed without the infrastructure overhead:

  • PostgreSQL - A relational data model that naturally fits budgeting (budgets have categories, categories have transactions, households have members)
  • Authentication - Email, OAuth, and magic links without building auth flows from scratch
  • Row Level Security - Multi-tenancy enforced at the database level rather than hoping I got every API endpoint right
  • Edge Functions - Server-side logic in TypeScript for push notifications and recurring transaction generation
  • Realtime subscriptions - Live updates when data changes

Realtime is a must-have for a household budgeting app. When my wife adds a transaction on her phone, I should see it appear on mine within seconds and vice versa. It's 2026—users expect real-time updates in their mobile apps. A refresh button feels broken. But building a sync engine from scratch? That's weeks or months of work. Websockets, conflict resolution, reconnection logic, edge cases everywhere. Supabase gives you this out of the box. Subscribe to a table, get updates. What would have been a major infrastructure project became a few lines of configuration.

Why Legend State

State management in React Native is notoriously painful. Redux requires too much boilerplate for a solo project—actions, reducers, selectors, and all the wiring in between. Zustand is simpler, but it's still fundamentally imperative. You dispatch updates and hope your UI stays in sync.

Legend State takes a different approach. It's built on observables, which means your UI reacts to data changes automatically. When an observable value changes, only the components that depend on that specific value re-render. No selectors, no manual subscriptions, no stale state bugs.

But the real reason I chose Legend State was its Supabase plugin. It provides first-class support for syncing with Supabase, handling the hard parts out of the box:

  • Optimistic updates - UI updates instantly before the server confirms
  • Offline persistence - Data stays available via MMKV storage when offline
  • Conflict resolution - Handles race conditions when multiple users edit simultaneously
  • Automatic retries - Failed syncs retry intelligently without manual intervention
  • Realtime integration - Supabase realtime changes flow directly into your observables

When a user adds a transaction, the UI updates instantly (optimistic), the data syncs to Supabase in the background, and if they close the app and reopen it offline, everything's still there. The Supabase plugin handles retries, deduplication, and eventual consistency.

This architecture decision rippled through the entire app. I didn't need loading spinners on every screen. I didn't need to manage cache invalidation. The data layer just worked.

Why Expo

I chose Expo and React Native for a few reasons. First, familiarity—I've been working with React and TypeScript for years, so the mental model was already there. Second, cross-platform potential. While Tally launched on iOS first, Expo makes it straightforward to target web and Andriod users in the future without rewriting the app.

The developer experience is excellent. Hot reloading, EAS Build for CI/CD, over-the-air updates, and a managed workflow that handles most native configuration. I could focus on building the product rather than wrestling with two separate codebases in Xcode and Android Studio.

Why HeroUI Native

I did not actually use HeroUI in my first prototype because I was not aware that it existed. I just used basic custom components to get a working product. Then I discovered HeroUI while scrolling Twitter and fell in love with the clean, simple components immediately. They looked polished out of the box but were flexible enough to customize with Tailwind. I knew right away that I had to integrate these components into Tally.

I wasn't trying to build a design system from scratch. I was building a budgeting app, and HeroUI let me focus on that and even gave me some ideas for new features. Buttons, inputs, modals, bottom sheets—all the building blocks were there. I could compose them into screens without spending days on component architecture.

The Product Decisions

Flexible Budget Periods

This was non-negotiable. One of the main points of Tally was to escape the calendar-month prison. I designed budgets to have arbitrary start and end dates. Want to budget from the 15th to the 28th? Done. Want a weekly budget? Done. Need to budget based on credit card statement? Done.

The implementation was simple. Transactions are added to the active budget, and the budget tracks its own period. No complex date calculations or period-spanning logic. Simplicity is the focus.

Manual Entry by Design

Every other budgeting app tries to automate transaction entry through bank connections. Half of the time, the transaction is categorized incorrectly so you end up spending more time fixing transactions or you are left with an inaccurate representation of your spending. I went the opposite direction: manual entry only.

This wasn't a limitation, it was a feature. When you manually log every purchase, you feel the money leaving. You become more intentional. My wife and I have used this approach for years, and the mindfulness it creates is worth the 10 seconds per transaction.

Household Sharing

While many people create solo budgets, we value transparency and budgeting as partners in my household. So, I thought having the ability to share budgets with your partners or roommates was an important feature to include. That led to creating households, where a user can create a household, invite members, and share budgets within your household.

The tricky part was RLS policies. When a budget is shared with a household, all members need to see every transaction in that budget. The policies had to traverse the relationship chain. Transaction belongs to budget, budget is shared with household, user is member of household. Getting those joins right while keeping queries performant took some iteration.

Real-Time Collaboration

Live updates were implemented using Supabase's realtime subscriptions combined with Legend State's observable sync. When I add a transaction to the current budget, a notification is sent to my wife's phone and she sees the latest transaction data within a second. No refresh button, no stale data, no "pull to refresh" patterns just to see the latest data. The app just stays in sync.

The Technical Decisions

Row Level Security Everywhere

I made an early decision. Every table gets RLS policies, no exceptions. If you are going to use Supabase, then this is almost a requirement. Supabase does not recommend using their service without RLS policies. This forced me to think about security at the data layer rather than the application layer.

The pattern became simple. Users can access rows they created, or rows that belong to a household they're a member of. This single rule, applied consistently, made the security model predictable and auditable.

Edge Functions for Server Logic

Some things can't happen in the client. Push notifications need to batch and send efficiently. Recurring transactions need to generate on a schedule.

I built each of these as Supabase Edge Functions. They run close to the database, have access to admin credentials when needed, and deploy with a single command.

PostgreSQL Triggers for Automation

Recurring transactions were an interesting design problem. Users set up a template (amount, category, cadence), and the system generates actual transactions automatically.

I implemented this with PostgreSQL triggers and a cron job. When a recurring transaction is due, a trigger generates the next occurrence and calculates the following due date. A scheduled Edge Function runs daily to process any pending generations.

I also use triggers for soft deletes. When a user deletes a budget, I don't actually remove the row. A trigger sets a deleted_at timestamp and cascades that to related categories and transactions. This keeps referential integrity intact while letting me recover data if needed. The RLS policies filter out soft-deleted rows, so the app never sees them.

This keeps the complexity in the database where it belongs. The mobile app just reads transactions. It doesn't need to know or care that some were auto-generated or that deletions are reversible behind the scenes.

Iterating on the Product

The path from prototype to production wasn't linear. I'd build something, use it for a few days, notice friction, and redesign.

The backend stayed simple. A budgeting app doesn't need complex business logic. The database schema was straightforward and barely changed after the initial design. Most of my iteration happened in the UI.

Transaction entry went through the most revisions. This is the core interaction—if adding a transaction feels slow or clunky, people stop logging expenses. I obsessed over reducing taps, making category selection fast, and keeping the flow seamless whether you're standing in line at a coffee shop or sitting on the couch.

The dashboard evolved based on what I actually wanted to see. It started as a simple list of categories with spending totals. Then I added spend streaks to encourage daily logging, summary cards showing total spent and remaining, and progress bars for each category. Savings goals came later. Each addition came from using the app and wishing I could see something at a glance.

Budget creation and editing got similar treatment. The goal was always the same: make sure all relevant information is visible and every action feels effortless.

What I Learned

The Right Tools Collapse Complexity

Tally isn't a complex app. But a few years ago, building it would have meant stitching together a dozen services, writing boilerplate for auth, managing websocket connections, and debugging sync conflicts. The features themselves aren't hard—the infrastructure used to be.

Supabase, Legend State, Expo, and HeroUI collapsed all of that. Real-time sync, offline persistence, push notifications, recurring transactions—these became configuration problems instead of engineering projects. I spent my time on the product, not the plumbing.

The UI is the Product

For a consumer app like this, the UI is the product. The database schema matters—get the relationships wrong and you'll fight your data model. But schema design is relatively straightforward: budgets have categories, categories have transactions, households have members. Those relationships are obvious once you think about them.

What's not obvious is deciding what data to display, where to display it, and how to make every interaction feel effortless. I spent more time on UI decisions than any other part of the project. Which metrics belong on the dashboard? How many taps to add a transaction? What should a user see first when they open the app? These questions don't have clear answers in a database schema.

The database enables the product. The UI is what users remember and what keeps them coming back.

Iteration Beats Planning

I could have spent weeks writing detailed specs. Instead, I built a prototype, used it, and iterated. The final product looks nothing like what I imagined at the start. It's better, because it evolved based on actual use.

The feedback loop was tight: build something, use it while budgeting with my wife, notice what's annoying, fix it. Repeat.

What's Next

Tally Budget is currently in beta testing on iOS devices in Test Flight. Please reach out to willrowston@gmail.com if you are interested in becoming a beta tester for a free year of Tally!

I'm actively working on AI assisted budgeting, spending power calculator, home screen widgets, and Google/Apple provider sign up support.

Each feature goes through the same process. Prototype, use, iterate, ship.

Final Thoughts

Building Tally reinforced something I've always believed: the best tools are the ones that get out of your way. Supabase handles the backend complexity. Legend State handles state and sync. Expo handles the build pipeline. HeroUI handles the component design. Each layer let me focus on the next.

I wanted a budgeting app that fit how my wife and I actually manage money. Now we use it every day.


Try Tally Budget: tallybudget.app

Read more: The Shift: When the Tools Finally Caught Up