Calm, real double-entry bookkeeping for sole proprietors who sell services.
simple-books is a bookkeeping platform built for one person: the owner of a
service business who does not need (or want) QuickBooks. It produces a
real set of books — every entry posts to a balanced double-entry journal —
without ever asking the user to read one.
- Service products — what you sell, with a rate per unit (hour, session, project…).
- Customers — just enough info to attach to invoices.
- Invoices — line items, automatic numbering (
YYYY-NNNN), status (open · paid · void). Records only — no email, no payment processing, no PDFs - this is for you, it is not a customer portal. Edit open invoices with no payments (reverses and reposts the journal entry). - Cash receipts — log payments as they come in. If there isn't an invoice yet, one is created automatically and the same transaction posts it. Method-aware (cash, check, card, transfer, other). Stand-alone payments keep the auto-created invoice in sync.
- Mileage — record business driving with the IRS standard rates
(default
$0.725/milein 2026); each trip credits Owner's Contribution and debits Vehicle Expense. Trips can be edited after the fact if needed. - Reports — Balance Sheet and Cash Flow, rendered as readable tables. No fancy charts, by design. Assets always equal Liabilities + Equity.
Behind the scenes every event becomes a balanced journal entry — see
DESIGN.md §5–7 for the data model and posting rules and
.agents/skills/accounting-posting/SKILL.md
for the engineering recipe.
| Layer | Choice |
|---|---|
| Runtime | Bun |
| Build | Vite |
| Server | Nitro (Bun preset, via nitro/vite) |
| App | TanStack Start (React + TanStack Router + TanStack Query) |
| Styling | Tailwind with a custom OKLCH theme and Google's DESIGN.md pattern |
| DB | SQLite (local dev) or PostgreSQL (production) via Drizzle ORM |
| Auth | Better Auth — email/password + generic OIDC plugin |
| E2E | Playwright (bun run build:e2e && bun run test) |
No Next.js. No Node. No React server components. ESM only.
# 1. Install Bun if you don't have it
curl -fsSL https://bun.sh/install | bash
# 2. Install + initialize
bun install
cp .env.example .env
bun run auth:secret # copy the printed value into BETTER_AUTH_SECRET
bun run db:migrate
bun run db:seed
# Optional: PostgreSQL (recommended for any real deployment)
# docker run -d --name simple-books-pg -e POSTGRES_USER=simplebooks \
# -e POSTGRES_PASSWORD=secret -e POSTGRES_DB=simplebooks -p 5432:5432 postgres:18
# export DATABASE_URL=postgresql://simplebooks:secret@localhost:5432/simplebooks
# bun run db:migrate && bun run db:seed
# 3. Dev server (http://localhost:3000)
bun run devThe very first visit to /login will offer to create your owner account
— there is only one owner per install. Subsequent visits show normal sign-in
and (if configured) the OIDC button.
Fill in the four OIDC_* env vars in .env. The discovery URL is built
from OIDC_ISSUER_URL. Register the redirect URI as
<BETTER_AUTH_URL>/api/auth/oauth2/callback/oidc in your IdP.
SSO behavior (operators): When OIDC is configured, successful sign-in from your IdP will auto-provision a new app user if that email is not already in the database, or link the OIDC identity to an existing user when the email matches (for example the owner who first signed up with email/password). Access control is entirely through your identity provider — do not wire up arbitrary “social login” OAuth apps (Google, GitHub, etc.) unless you intend everyone who can use that client to reach the books. After the first user exists, additional people should be added in your IdP, not via public email sign-up. The system is really intended to be used with OIDC, even if lightweight. Auth0 is one of many good free options for small teams.
- SQLite (
DATABASE_URL=./data/simple-books.db) — zero setup, good for local dev and quick demos. Not recommended for production: the database file lives on disk (or a PVC) and can be lost if the volume is deleted, the node fails, or the file is corrupted, or is manually edit and corrupts the SQLite WAL. - PostgreSQL (
DATABASE_URL=postgresql://…) — use for any real-world install. Helm can bundle a Bitnami Postgres subchart (postgresql.enabled) or point at managed Postgres (database.externalUrl). Argo CD / GitOps: use an external Secret for Postgres credentials (postgresql.auth.existingSecretordatabase.existingSecretName); seedeploy/helm/simple-books/README.md. Migrations live indrizzle/pg/; SQLite migrations stay indrizzle/.
bun run db:generate # SQLite schema changes
bun run db:generate:pg # PostgreSQL schema changes
bun run db:migrate # applies the right folder for DATABASE_URLbun run build # -> .output/server/
bun run start # -> bun run .output/server/index.mjsbun run build:e2e # production build with frozen REFERENCE_DATE
bun run test # prod server + ephemeral SQLite DBThe test walks the entire sole-proprietor flow. To refresh committed README images in
docs/screens/, run bun run screenshots.
Normal bun run test does not touch those files.
PROMPT.md— original task, frozen.DESIGN.md— architecture, data model, posting rules, security posture, visual design system. The source of truth for design consistency..agents/skills/— Agent Skills (skill-name/SKILL.md) for stack, posting, UI, deployment, and testing.
| Page | Screenshot |
|---|---|
| Dashboard | ![]() |
| Invoices | ![]() |
| Mileage | ![]() |
| Balance Sheet | ![]() |
| Cash Flow | ![]() |
MIT. See LICENSE.




