The Shrinking Monolith: Inside sayyedaaman2/expense-tracker

How a minimalist local-first finance app serves as a boilerplate for the zero-dependency database era using Express 5 and native Node.js SQLite.

6 min read • View on GitHub • More from sayyedaaman2

A massive, bloated cargo ship patched with dependency warnings is passed by a sleek, minimalist wooden skiff. This illustrates the contrast between legacy Node.js dependency hell and modern native-first stacks.
The modern Node.js stack is actively cannibalizing external dependencies.
Key Takeaways

The Standard Library Absorbs the Database

For years, building a Node.js application with SQLite meant wrestling with native bindings, Python build chains, and node-gyp. The alternative was adopting a heavy ORM that abstracted the database away but added massive overhead to the node_modules folder.

The expense-tracker repository demonstrates a profound shift. By utilizing the new import { DatabaseSync } from 'node:sqlite', it bypasses the traditional dependency hell entirely. The standard library is now capable enough to handle persistent local storage without external drivers, resulting in a drastically lighter and faster installation process.

Bleeding Edge by Default

Beyond the database layer, this project serves as a time-capsule for the latest major version bumps across the JavaScript ecosystem. It wires together Express 5.1, which finally brings native Promise handling out of beta, alongside Tailwind CSS 4.0 and React 19.

The Zero-Dependency Data Flow in Express 5.

Hand-Rolled SQL Meets Type Safety

Instead of relying on Prisma or Drizzle, the author eschews ORM magic for a manual dynamic SQL builder. Fields and values are pushed into arrays to construct raw UPDATE statements based precisely on the incoming payload.

if (payload.title !== undefined) {
    fields.push("title = ?");
    values.push(payload.title);
}

This raw approach is made safe by Zod, which acts as a strict bouncer at the network layer. It ensures that only validated, expected schema properties ever reach the SQL builder, marrying low-level database control with high-level type safety.

A close-up of a workbench. On the left, a heavy, complex brass magnifying glass with multiple thick lenses folded out. On the right, a simple, surgically sharp scalpel resting on a blueprint. This represents the precision of raw native SQL versus the heavy abstraction of an ORM.
Precision over abstraction: raw SQL paired with strict network validation.

The Developer's Dilemma: Build vs. Host

Why do developers continually rebuild expense trackers? The motivation is rarely about lacking features in commercial tools; it is a rejection of latency and data-harvesting. The open-source personal finance space is driven by a desire for absolute data privacy and lightning-fast local data entry.

I wanted to create something that would be: Smart enough to give insights about our spending, Simple enough that we’d actually use it daily, Mobile-first (because who carries a laptop to record coffee expenses?), Lightning fast to use (under 5 seconds to add an expense)

Gurpreet Singh, Developer · Building My First Side Project
ProjectStackParadigmBest For
expense-trackerNode / Native SQLiteBarebones Monorepo-liteEducational boilerplate, minimalists
ActualNode / ReactLocal-first sync engineUsers wanting a polished offline experience
Firefly IIIPHP / LaravelDouble-entry monolithic serverPower users needing complex instruments