Atlas
Personal finance dashboard
Atlas is a personal finance dashboard I built to replace the Google Sheet I had been tracking my finances in for 4+ years. It tracks four kinds of assets in one place: cash, ETFs, crypto and resale inventory.
Access is invite-only: a whitelist gates both sign-up and every subsequent login.
Highlights
Architecture
A single Next.js deployment on Vercel, where server actions replace the REST layer entirely and everything lives in one Postgres database.
Key Challenges
Balances that stay right when history arrives late
Backend I add old transactions months after the fact, and the balance chart still has to be correct.
Balances that stay right when history arrives late
Each account gets a day-by-day running total, and the whole curve is then shifted so it passes through the balance I know the account started at. Shifting instead of cutting off at that date is what makes late entries safe: a transaction dated before it was already inside the starting balance, so it gets subtracted back out rather than counted twice.
- ·An account with no known starting point is left unshifted, which is just a plain running total.
- ·Quiet days still produce a point, so the chart has no gaps to guess across.
- ·The current balance is worked out a different way from the same rows, and the two have to agree on today.
One transfer, two accounts
Backend Moving money between my own accounts changes two balances, but every query assumes a row belongs to one.
One transfer, two accounts
A transfer is two rows sharing a group id, with the outgoing leg stored as a negative amount. Balance queries can then treat it exactly like income and the negative leg subtracts itself, so there is no special case anywhere. The two legs carry their own amounts rather than mirrored ones, so a wire fee or a conversion still leaves both accounts at their real balance.
- ·Filtering by one account returns one leg, so the list fetches its companion and still shows the transfer as one thing.
- ·Counting transactions collapses the pair, otherwise every transfer would count twice.
- ·Editing one runs as a single database transaction, so a failure can't change half a transfer.
A photo link the server has to open
Backend Pasting an image URL asks my server to go fetch it, and my server can reach things the internet can't.
A photo link the server has to open
The address is checked after it resolves, not just the hostname, and anything internal — private ranges, loopback, cloud metadata — is refused before the request leaves. Redirects are followed one hop at a time and re-checked each time, since an allowed host can still point somewhere private.
- ·The download is cut off mid-stream at 5 MB rather than fetched and then rejected.
- ·Everything accepted is re-encoded, which also drops whatever metadata the original carried.
- ·If the database row fails to save afterwards, the file is deleted so nothing is left orphaned.
Evolution
Replaced a hand-maintained Google Sheet with a standalone product.
Google Sheets Atlas Google Sheets
Months as columns, a Cashflow tab of Type→Category rows, dates split across Month/Day columns with the year implicit, and no year-over-year view.
Atlas
One place to track all personal (and business) finances, where balances, net worth, cashflow and portfolio value are all derived at read time.
Complete Stack
Next case study
Lab19
Shopify price automation platform
Need something like this?
I build custom internal tools and automation systems around real business workflows, from pricing pipelines to inventory dashboards. I'm not taking new client work at the moment, but I'm happy to hear about what's next.
Get in touch