DEV Community

Cover image for Building a Privacy-First Finance App with Laravel 12 and React 19
Víctor Falcón
Víctor Falcón

Posted on

Building a Privacy-First Finance App with Laravel 12 and React 19

Every personal finance app wants your bank credentials. I built one that doesn't.

The Problem

I've been tracking my finances digitally for years. Every app I tried followed the same pattern: "Connect your bank account to get started." This means handing your credentials to a third-party aggregator like Plaid, which in turn means your complete transaction history flowing through systems you don't control.

After seeing multiple fintech data breaches make the news, I decided to take a different approach.

The Approach

Whisper Money is a personal finance app that never connects to your bank. Instead, you export a CSV or XLS from your bank (which every bank supports) and import it. A year of transactions loads in about 10 seconds.

The trade-off is clear: no real-time sync, but complete privacy. For me, importing once a week takes less than a minute and is worth the peace of mind.

The Stack

I built this with the latest Laravel ecosystem:

  • Laravel 12 (PHP 8.4) - The backend handles accounts, transactions, budgets, categorization, and user management
  • React 19 - The frontend with full TypeScript strict mode
  • Inertia.js v2 - Bridges Laravel and React without building a separate API. Deferred props, prefetching, and the new Form component are used throughout
  • Tailwind CSS v4 - CSS-first configuration with the @theme directive
  • Laravel Wayfinder - Generates type-safe TypeScript functions from Laravel routes. No more hardcoded URL strings in the frontend
  • Pest v4 - Test suite with factories and feature tests

Architecture Decisions

Why Inertia instead of a separate API?

For a product like this, Inertia is perfect. You get the SPA experience (client-side routing, smooth transitions, reactive forms) without maintaining a separate API layer. The backend renders props, the frontend renders components. Authentication, authorization, and validation all happen server-side through standard Laravel patterns.

Why no bank integration?

Beyond privacy, this is also an architectural simplification. Bank API integrations are complex: they require handling multiple aggregators, dealing with connection failures, managing OAuth flows, and storing sensitive credentials. By removing this entirely, the codebase stays focused on what matters: helping users understand their spending.

Dual distribution: OSS + SaaS

The same codebase powers both the self-hosted version and the hosted SaaS. Laravel Pennant feature flags control what's available in each context (e.g., Stripe subscriptions are only enabled for the SaaS version). This keeps everything in one codebase without maintenance overhead.

Key Features

Automation Rules with JSON Logic

Users can create rules to auto-categorize transactions without writing code. Under the hood, this uses JSON Logic - a portable, serializable way to represent conditional logic. Example rule: "if the transaction description contains 'Netflix', set category to 'Subscriptions' and add label 'recurring'."

Smart Budgets

Budgets support different periods (daily, weekly, monthly, yearly) and rollover types. If you underspend in one period, the remainder can roll into the next.

Self-Hosting Made Easy

The project includes a production Docker image, a Docker Compose file, and a Coolify one-click deploy template. Getting it running on your own server takes minutes, not hours.

Try It

The project is open source under CC BY-NC 4.0. Contributions welcome.


What's your take on the "no bank integration" trade-off? Would you give up real-time sync for complete privacy over your financial data?

Top comments (0)