KHMYS (Know How Much You Spend) is a personal finance web app I built to bring everyday spending, budgets, accounts, investments and goals into one place. This post is a tour of how it is put together and why. It is written for developers who are curious about the choices, and for anyone who wants to know what sits behind the app they use.
The shape of the system
KHMYS has three main parts:
- A web app that people use in the browser.
- An API that holds the logic and talks to the database.
- A database that stores accounts, transactions, budgets and everything else.
Two other services sit around them: Google handles sign-in, and Plaid handles optional bank connections.
The frontend
The web app is a single-page application written in TypeScript with React and built with Vite. Styling uses Tailwind CSS with a small set of design tokens, so the landing page, the app and the brand kit share one look. TanStack Query handles server data and caching, and React Router handles navigation.
Two details are worth calling out:
- The landing page is pre-rendered. A single-page app normally sends an empty HTML shell and fills it in with JavaScript, which is poor for search engines and slow for first paint. At build time, a script renders the landing page to plain HTML, so crawlers and visitors get real content immediately. The app takes over after that. Signed-in visitors skip the static copy.
- The policy pages and guides are static HTML. The privacy, terms and security pages and this blog are generated files with no JavaScript required, which keeps them fast and easy to index.
The API and database
The API is written in Python with FastAPI. Data lives in PostgreSQL, accessed with SQLAlchemy 2 and versioned with Alembic migrations. Every record belongs to a user, and every query is scoped to the signed-in user.
Sensitive provider tokens, such as the access token Plaid issues for a connected bank, are stored encrypted using the Python cryptography library rather than as plain text.
Sign-in and sessions
KHMYS has no password system. Sign-in is through Google, and the API verifies Google's ID token on the server. After that, the browser gets a session cookie that is HttpOnly, Secure and SameSite, and state-changing requests carry a CSRF token. Sessions expire, and you can see and end them from Settings. The safest password is the one the app never has to store.
Bank connections
Bank connections use Plaid. You enter your bank login inside Plaid's window, and KHMYS only receives an access token and the data you allow. KHMYS asks for the transactions product, not the ability to move money. If you disconnect, KHMYS asks Plaid to remove the connection and deletes the stored token.
Because not everyone wants to link a bank, there is also a statement importer for CSV and PDF files, and you can enter transactions by hand. The importer lets you check the column mapping and preview rows before anything is created, and it skips rows that look already imported.
Security headers and the demo
The site is served with strict security headers, including a content security policy, HSTS and frame protections. API keys are shown once and stored only as hashes, with scopes and expiry.
The demo at demo.khmys.com is intentionally separate. It runs on its own host with a shared read-only account filled with fictional data, so it can never touch a real person's session or data.
Testing
The test suite runs the API against a real PostgreSQL database, not SQLite. The schema uses PostgreSQL features such as JSONB and INSERT ... ON CONFLICT, and a passing test on a different database would prove very little. The frontend has component and integration tests with Vitest and Testing Library. After a few bugs caused by unusual data, I also render-test pages with deliberately messy dates and currencies before shipping.
Running it
The pieces run as Docker containers behind Cloudflare, which provides DNS, TLS and a tunnel to the server, with nginx serving the built web app. Deployments happen on push to the main branch.
What I would tell another builder
- Make the first page real HTML. If you want search traffic, a blank shell is a handicap.
- Store less. Not having passwords or card numbers removes whole categories of risk.
- Test where you deploy. Use the same database engine in tests as in production.
- Write down what the app does with data. Our privacy page and security page are written to match what the code actually does, and they get updated when it changes.
Try it
You can explore the read-only demo, sign in at khmys.com with Google, or read the practical guides on this blog, starting with how to track your spending. KHMYS does not charge anything today. If you have questions about how it works, email [email protected].