Case · Go backend
Ledger: personal finance tracker
Personal finance tracking: accounts in different currencies with manual exchange rates, income, expenses and transfers, budgets, payments on a cron-like schedule, CSV import with a preview, reports. Money is stored only in integer minor units, users' data is isolated, and the charts are drawn in SVG with no library. Go + SQLite with a Nuxt 4 front end.
- Go
- SQLite
- modernc.org/sqlite (no CGO)
- REST API
- Nuxt 4
- Vue 3
- TypeScript
- Pinia
- SVG charts
- vitest
- ESLint
- Docker
- GitHub Actions
- PBKDF2
- CSV import/export
01 · How it looks
Screenshots


02 · About
What it is and why
Ledger is a self-hosted personal finance tracker: accounts in different currencies, income, expenses and transfers, budgets with warnings, scheduled payments, CSV import from a bank and reports. One Go binary with SQLite inside and a Nuxt 4 front end embedded.
The point is the money: everywhere, in the database, the API and the browser, it is stored as integer minor units (cents), and currency conversion goes through big.Int with rounding. The charts are drawn in SVG with no library. In demo mode all data in the interface is marked as made up.
Limitations
Exchange rates are manual and current only: no rate history is stored, so the past is recomputed at today's rates and the exchange-rate difference is not visible. The base currency and an account's currency cannot be changed.
A budget is the same every month, with no carry-over. Scheduled payments are created when someone opens the app or presses a button: there is no background scheduler.
There is no password change or recovery and no e-mail confirmation. SQLite and a single process: fine for personal or family use, not for high load. style-src still allows unsafe-inline (Vue styles), and SSR is off, so this is an SPA shell.
The -race tests run only in CI (there is no gcc locally), and the Docker image has not been built.
What is inside
- Accounts in different currencies and manual exchange rates (stored as an integer x10^6); totals and reports are computed in the base currency
- Income, expenses and transfers between accounts, including across currencies; tags, search and filters
- Money without floats: integer minor units, conversion through big.Int with round-half-away-from-zero, and tests for amount parsing and rounding
- Budgets: a monthly limit per category, a warning threshold, and the statuses "within", "close" and "exceeded"
- Payments on a cron-like rule of five fields (@monthly, */10, ranges, weekdays); missed dates are caught up
- CSV import: delimiter detection, a preview, column mapping, Windows-1251 encoding, a dry run with no writes, duplicate skipping and a report of bad rows; CSV export protected against formula injection, and a full JSON export
- Reports: net worth over time, spending and income by category, month over month and trends (a least-squares slope, in integers)
- User isolation: every request is bound to the user from the session, and tests check that other users' objects cannot be read or changed
- Hand-made SVG charts (line, bars, horizontal bars, sparkline) with role="img" and a text table for screen readers; light and dark themes, keyboard shortcuts
03 · How to run
Run it locally
git clone <repository on request> finance-tracker
cd finance-tracker
SEED_DEMO=1 go run . # http://localhost:8102, a demo user with made-up data for 12 months04 · Code
The repository is private
The source lives in a private repository. I will grant access on request: write to me and I will send an invitation.
Request the code