🧳 Itinerary Tracker

How it's built.

A multi-tenant trip-planning app that runs entirely on Cloudflare's edge. First-party auth, a data layer that isolates every group from every other, and a settle-up engine that does the who-owes-whom math in integer cents. Built solo, end to end.

The stack

Decisions that shaped it

It runs at the edge, as one Worker.

The whole app is a single Cloudflare Worker in front of a D1 database. There's no origin server to keep alive and no region to be far from. The frontend is hand-written HTML, CSS, and JavaScript with no framework and no build step, so it loads fast and there's very little that can break between me and the browser.

First-party auth, no passwords.

Sign-in is an emailed magic link (single-use, stored only as a hash, expires in 15 minutes) or Google. The session is a signed token that lasts a year, because the worst thing this app knows is a door code, so the right tradeoff is to almost never make people re-auth. I dropped Cloudflare Access on purpose: its free tier caps at 50 seats across the whole account, and a friend group plus partners blows past that.

One group can't see another's data.

Every group is a tenant. A single data-access layer binds every query to one group's id, enforced in one place so no route can forget it and read across tenants. Balances and household weights are never stored; they're derived on read, so they can't fall out of sync with the expenses and people they come from.

One login, many groups.

The same email can belong to a friends trip and a separate family trip, each fully walled off, with a switcher in the corner. Identity stays in the signed token and never changes; a separate cookie picks which group you're looking at. So switching groups is instant and never touches the login.

The settle-up engine.

Splits can be weighted by household size, even, or exact amounts, and all of it is computed in integer cents so floating-point rounding never leaks a penny. Then a greedy pass collapses everyone's net position into the fewest payments that settle the group. It only ever does the math. It never moves money, connects to a bank, or holds a balance.

Built to move, and to ship safely.

It's API-first, so the same JSON endpoints that serve the web app could serve a native app later. The database sits behind one small driver, which keeps a future D1-to-Postgres swap contained. And deploys run through a script that applies database migrations first and only ships the new code if they succeed, so new code never lands on an old schema.

What it is, honestly

A coordination tool, not a payment app. It tracks who owes whom and shows the cleanest way to settle, then gets out of the way. It never connects to a bank or moves a dollar, and it's invite-only by design.

See how it works