budget: The Vanilla JS App That Shows the Plumbing Behind Modern UI Frameworks

A single-file budgeting tool built with the Module Pattern, manual DOM updates, and old-school discipline. It is a small app with a big lesson about how frontend state really works.

9 min read · zubair-trabzada/budget

A wide editorial scene of a tidy desk turned into a machine room, with a ledger page, calculator, and paper slips feeding into a central control box. The image explains that this budgeting app is less about finance than about the hidden coordination that keeps state and interface in sync.
The app looks like a budget tracker, but its real subject is coordination.
Key Takeaways

The hidden lesson in a tiny budget app

Most budget apps promise insight. budget promises something stranger and more useful to builders: it exposes the plumbing. The repo is a browser-only app that keeps income, expenses, totals, and percentages in sync with vanilla JavaScript and a carefully divided module structure.

That matters because the hard part of UI software is not drawing boxes. It is keeping state, DOM, and user actions from drifting apart. This project makes that coordination visible instead of hiding it behind a framework.

One file, three responsibilities

The whole application lives in app.js, but it is not a monolith in the sloppy sense. It is split into budgetController, UIController, and a global controller. The model owns the data and math, the view owns selectors and rendering, and the controller does the handoff between them.

var budgetController = (function() {
  // data and calculations
  return {
    addItem: function(type, des, val) { ... },
    calculateBudget: function() { ... }
  };
})();

var UIController = (function() {
  // DOM selectors and rendering
  return { ... };
})();

var controller = (function(budgetCtrl, UICtrl) {
  // event listeners and orchestration
})(budgetController, UIController);
A close-up editorial illustration of three interlocking mechanical drawers, each with different contents and moving parts. One drawer holds ledger sheets, one holds DOM fragments, and one carries the gears that route events between them, showing how the app separates responsibilities without a framework.
The architecture is simple because each part has one job.

The app is a loop, not a list of isolated functions.

Why manual updates are the point

In a modern framework, change the state and the UI usually follows. Here, every step is explicit. Submit the form, parse the input, create an item object, push it into the right array, recalculate totals, format the numbers, inject markup, then refresh the percentage badges on every expense row.

That is the lesson. Frameworks reduce ceremony, but they do not erase coordination. This app keeps that coordination visible, which is why it reads like a systems diagram disguised as a budget tracker.

The little ID trick that keeps the list honest

The sharpest little detail in the app is the ID strategy. New items look at the last element in the array and add one. In a local-only browser app, that is enough. It is also a good reminder that identity is a design choice, not magic.

ID = data.allItems[type][data.allItems[type].length - 1].id + 1;

The trade-off is visible too. Delete the last row and the next number still advances. That would be a smell in a distributed system. Here it is fine, because the number only needs to be unique inside one session.

A medium-close editorial illustration of numbered cards being appended to a ledger, with the newest card lifted to reveal the next number stamped beneath it. The image explains why the app can generate simple local IDs without a database and still keep the list consistent.
The ID is small, local, and good enough for the job.

This is what a framework hides

Seen next to modern frameworks, budget looks almost old-fashioned. That is the point. It does the same coordination work, just with fewer abstractions and more explicit responsibility. The comparison is not about winners. It is about what each stack chooses to make visible.

ProjectState modelStorageWhat updates the UIMain strengthMain trade-off
budgetManual arrays plus calculated totalsIn-memory browser stateController calls DOM methods directlyThe plumbing is transparentIt demands discipline at every step
React or Vue appDeclarative component stateComponent state plus app storeRenderer reacts to state changesComposition scales wellThe coordination is abstracted away
Actual BudgetZero-sum budgeting with local-first syncLocal data plus optional sync serverDedicated app workflows and viewsOpinionated budgeting with strong data ownershipHeavier setup and a larger surface area
Plain text accountingLedger entries in text filesFiles on diskCLI tools and report generationPortable, scriptable, durableLess immediate for day-to-day spending entry
A split editorial illustration showing two ways of building a budgeting UI. One side is tangled with loose sticky notes and disconnected arrows, while the other side uses clean channels and labeled control paths to move updates through the app. The contrast explains what frameworks hide and what the repo makes explicit.
The contrast is not better versus worse. It is hidden versus visible.

Why it still matters

The code reads like an educational or portfolio project from the era before hooks normalized component state. That does not make it dated. It makes it legible.

You can hand this repo to someone learning frontend architecture and they will see the problem first, then the solution. That is rarer than it sounds. Most small apps work, but few show their own mechanics this cleanly.