A forward-looking checkbook for the account that pays your bills. It projects the balance forward and names the exact date it runs dry — so the bills stay funded, and whatever sits in your other account is genuinely yours to spend.
BananaBook is built to support a specific, deliberately simple way of managing money — one designed to reduce financial stress and to minimize the time spent thinking about it at all.
- Open a separate account just for recurring bills.
- Automatically divert a fixed portion of every paycheck into it, enough to cover those bills as they come due.
- Spend freely from whatever is left. The balance in your everyday account is money you can spend without arithmetic, because every bill is already funded from somewhere else.
The appeal is what it removes. No categories, no envelopes, no weekly budget review, no deciding whether this purchase is affordable. One number in one account answers that.
The hard part is step 2 — picking the transfer amount, and knowing whether it is still right six months later. Bills change. Annual costs land in a lump. Set the transfer too low and the bills account quietly drains until something bounces; set it too high and money that could have been spent or invested sits there doing nothing.
That is the job BananaBook does. It models the bills account so you can:
- Size the transfer — adjust the recurring deposit and watch the projected low point move until it settles where you want it.
- Reconcile quickly — confirm each bill as it lands, at the estimated amount in one click or at what actually hit the account, so the forecast stays true.
- See a shortfall coming — with a date attached, months before it happens.
- Catch over-funding — if the projected low point never dips near zero, you are parking more than you need and can dial the transfer back.
Most budgeting tools are rear-view mirrors: they categorize what already left your account. That is useful for judging the past and useless for answering the question that actually matters — are the bills covered, and for how long?
BananaBook answers with a date. In the screenshot above, the monthly bills account holds $1,647.40 and looks perfectly healthy. It isn't: the transfers are running about $118/month short of what the bills actually cost, so the account drains a little each month and rent is the line that finally bounces. It crosses zero on December 1, 2026 at -$304.60, and keeps sinking to a low of -$1,320.60. That is 134 days of warning — long enough to raise the transfer by a few dollars a paycheck and never notice the problem again.
Nothing in that view is a category or a chart. It is the ledger you would have written out by hand, run forward.
- Balance projection — every account projected forward from a starting balance plus recurring deposits and expenses, over a horizon you choose (1 month to 2 years).
- Overdraft date — the headline verdict on every account: how many days until the balance goes negative, on what date, and how deep it goes.
- Recurring transactions — daily, weekly, bi-weekly, monthly, or yearly, with optional end dates, and the ability to deactivate one without deleting its history.
- The reconcile loop — confirm each projected item as it happens, either at the estimated amount (one click) or at what actually hit the account. The forecast you trusted last month stays honest this month.
- Projected vs. actual — reconciling at a different amount records the variance instead of hiding it, and can optionally update the recurring estimate going forward.
- Lowest projected balance — the floor the account is heading for over the whole horizon, which is what tells you whether you are under- or over-funding the transfer.
- Multiple accounts — split the bills that are hard to hold in your head from the ones that aren't: a monthly account for the steady stuff, an annual account for the lumps. Each is projected independently.
- No account linking — nothing connects to your bank. You type the numbers in; they stay on your machine.
- Light and dark — follows your system theme by default, with a switch in the header to pin either one. The choice is stored in the browser, per device.
Splitting the bills across two accounts is worth doing because the two kinds fail differently. Monthly bills are steady enough that a shortfall hides for months — the case above. Annual bills do the opposite: nothing happens, nothing happens, and then property tax lands in one lump. Holding them apart means a bad month in one never disguises itself as a good month in the other.
Note what is not here: the everyday spending account. It carries no recurring items and needs no forecast, so BananaBook does not track it at all. Its balance is already the answer — that is the entire point of funding the bills from somewhere else.
Read this before you deploy anything.
BananaBook has no authentication whatsoever. There are no users, no login, no sessions, and no authorization checks. Anyone who can reach the port can read, edit, and delete every financial record in the database.
That is a deliberate design choice for its intended use — a single self-hosted household instance on a trusted home network, where everyone in the house is meant to see the same numbers. It is emphatically not safe to expose to the internet.
Run it on localhost or a trusted LAN only. If you need remote access, put it behind
something that actually does authentication — a VPN such as WireGuard or Tailscale, or
a reverse proxy enforcing auth in front of it. Do not simply forward a port.
Also note:
- There is no rate limiting, CSRF protection, or audit logging.
- The SQLite database is stored unencrypted on disk.
synchronize: trueis enabled in TypeORM (see Known limitations).
Found a security problem? See SECURITY.md.
Requires Node.js 20 or newer.
git clone https://github.com/majoragee/bananabook.git
cd bananabook
npm install
npm run devOpen http://localhost:3000. The SQLite database is created automatically on first run; there is no migration step and no seed data.
To set it up the way the method intends: create one account for your bills, set its starting balance to whatever is in the real account today, then add your recurring bills as expenses and the automatic paycheck transfer as a deposit. The projection — and the date you run dry — appears immediately. Adjust the transfer amount until the lowest projected balance sits at a cushion you are comfortable with.
npm testThe suite covers the projection engine — frequency expansion, end dates, the running balance, the overdraft date, and the reconcile loop. It runs against a temporary SQLite database with the clock frozen, so it never touches your data and never depends on today's date.
A prebuilt image is published to GitHub Container Registry for linux/amd64 and
linux/arm64, so it runs on an x86 server or a Raspberry Pi without building
anything:
docker run -d \
--name bananabook \
-p 3000:3000 \
-v bananabook-data:/app/data \
ghcr.io/majoragee/bananabook:latestOr with compose, building from source instead:
cp docker-compose.yml.example docker-compose.yml
docker compose up -dThen open http://localhost:3000. Data persists in the bananabook-data volume.
Available tags: latest (most recent release), 0.1.0 / 0.1 (specific versions),
main (tip of the default branch), and sha-<commit> (an exact commit). Pin a
version tag for anything you care about — main moves.
Full deployment notes, including backup and restore, are in README.docker.md.
Copy .env.example to .env for local development.
| Variable | Default | Purpose |
|---|---|---|
NODE_ENV |
development |
Standard Node environment flag. |
DATA_DIR |
current directory | Where bananabook.db is written. Set to /app/data in Docker. |
PORT |
3000 |
The web port — the one you open in a browser. |
API_PORT |
3001 |
The internal API port. Change it if something else on the machine already uses 3001. |
API_HOST |
127.0.0.1 |
Interface the API binds. Loopback by default; there is rarely a good reason to change it. |
PORT and API_PORT are independent, so either can be moved without disturbing the
other, and both take effect at startup — no rebuild. If you run BananaBook on bare
metal and 3000 or 3001 is already taken, set the one you need:
PORT=8080 API_PORT=8081 npm startOr put them in a .env file — copy .env.example to .env and edit. Both processes
read it, in development and production alike. Anything already set in the environment
takes precedence over the file, so Docker's environment: block and a one-off
PORT=8080 npm start still win.
The API binds loopback and is reached through the web server's /api proxy, so it is
never a published surface: only PORT needs to be reachable from anywhere else.
server/routes/projections.ts builds the forecast for an account:
- Start from the account's
startingBalanceat itsstartDate. - Expand every active recurring transaction across the requested horizon, stepping by
its frequency and stopping at its
endDateif it has one. - Replace any projected item that has been reconciled with the real transaction, so actuals always beat estimates.
- Sort by date and accumulate a running balance.
- Report the first entry where the balance drops below zero — that's the overdraft date.
| Frontend | Next.js 16 (App Router), React 19, Tailwind CSS 4 |
| Backend | Express 5, TypeORM 0.3 |
| Database | SQLite via better-sqlite3 |
| Language | TypeScript |
| Deploy | Docker, multi-stage build, runs as a non-root user |
The design brief the interface is built against lives in PRODUCT.md.
Worth knowing before you trust this with anything important:
- No authentication. See Security and threat model.
synchronize: true. TypeORM alters the SQLite schema to match the entity definitions on every boot. Convenient in development, but it means a future schema change could alter or drop a column on upgrade. Back upbananabook.dbbefore updating. Proper migrations are on the roadmap.- Money is stored as floating point. Amounts are
decimalcolumns read back as JS numbers, so arithmetic can drift by fractions of a cent. Display is rounded to two decimals, but the underlying values are not exact. Integer-cents storage is on the roadmap. - A monthly item dated the 31st drifts. Months without a 31st roll the occurrence into the following month (Jan 31 → Mar 3), rather than clamping to the last day. There is a test pinning this behaviour so it can be fixed deliberately.
- Test coverage is partial. The projection engine is covered; the routes around it are not.
- Single currency, no formatting options. Amounts are rendered as USD.
- Test coverage for the API routes, not just the projection engine
- Clamp monthly items to the last day of short months
- Database migrations, replacing
synchronize: true - Integer-cents money storage
- Optional authentication for non-LAN deployments
- CSV import and export
Issues and pull requests are welcome — see CONTRIBUTING.md. This is a personal project built for one household's needs, so it is opinionated about scope; opening an issue before a large PR will save you time.
MIT © majoragee
AI assistance was used in the development of this project.