brewd

A cozy coffee journal for Android and iOS.

What inspired it

We're two people, one in Canada and one in the PNW, and between us we're never more than a few blocks from a good café. That's the whole origin. We wanted an excuse to build something together, and the worst case outcome was that we'd have to drink a lot of good coffee researching it.

The real itch was a specific kind of forgetting. You have something incredible in a city you're visiting, and six months later you can picture the cup and the window seat but not the name of the place or what you actually ordered. Notes apps are terrible at this. Every coffee app we found wanted to be a social network instead: feeds, followers, star ratings, people reviewing a café they walked past once.

So we set two rules early and kept checking ourselves against them. Nothing leaves your phone unless you ask it to. And the app never makes you feel bad. No streaks, no red badges, no "you haven't logged in 4 days." The rating scale bottoms out at a shrug emoji, because the worst thing you can say about a latte someone made for you is that it wasn't your cup.

The other constraint was speed. If logging a drink takes longer than fifteen seconds you will not do it standing at a counter with a cup in your other hand, and an empty journal is worth nothing.

How we built it

The app is Compose Multiplatform, one Kotlin codebase for Android and iOS, with the UI shared and the platform bits behind interfaces. The backend is a Cloudflare Worker (Hono, D1, KV, R2) in a Bun monorepo alongside the marketing site, an operator dashboard, and a CLI that ties the whole thing together.

The piece we're most glad we did early is the contract. We write our schemas once in zod, generate an OpenAPI spec from the routes, and generate the Kotlin API client from that spec. CI fails if the committed spec drifts from the code. It felt like over-engineering in week one. By week five it was the only reason one person could change a server response and have the client's compiler point at every place that broke.

Then we self-hosted more than we planned to:

  • The map. Map tiles get expensive fast, and we wanted a warm cream basemap rather than someone else's blue-grey. So we extracted a Protomaps planet archive, 127 GiB of it, streamed it into R2, and serve tiles out of the Worker. One file, no per-view billing.
  • The cafés. We ingest an OSM extract of cafés into D1, so the common case (what's near me right now) is a local query with no external call at all.
  • Auth. Self-hosted Better Auth on the same Worker and database, with email one-time codes instead of magic links.
  • Sync, for people who want their journal on more than one device. Records are opaque versioned rows, and photo bytes go straight to R2 through presigned URLs so they never pass through our Worker at all.

Subscriptions run through RevenueCat, which we kept deliberately thin: the app asks one question, "does this person have Plus," and everything else is a feature check.

Built With

Share this project:

Updates

Submission history