Kaapi
The itch
Kaapi started as Brew Notes. That was a private brew journal: you logged your beans, your gear and every cup, so a good result stopped being an accident you couldn't reproduce. It worked, and it was live, but it answered a question only its owner was asking. The bigger gap sat one step earlier. Nobody in India can currently answer "show me a light-roast washed coffee under ₹800 that suits a V60." The coffees exist, spread across a dozen roaster shops with a dozen ways of describing them, and there's no single place to look.
So the direction flipped. Kaapi is a public catalogue of Indian specialty coffee with the brewing knowledge attached, and the journal becomes the second half rather than the whole thing. The catalogue is the front door. Logging is the part that compounds.
What it does
The plan has three pillars.
- Find coffee. One catalogue across roasters, filtered by taste, price, roast level, process, origin and brewer, with in-stock as a first-class filter. Every coffee links plainly to the roaster's own product page, with no affiliate codes.
- Brew it. Reference recipes curated with attribution, community recipes attached to a specific coffee, and beginner guides for the ten or so common brewers.
- Make the next one better. A fast log with an optional guided timer. A completed log with a rating counts as a review, so nobody has to write prose.
The data model is Estate, then Offering, then Lot. The offering ("Blue Tokai Basankhan Washed") is the thing people review. Estates exist for browsing, and they collide across roasters all the time: Ratnagiri turns up at five of the eight launch roasters.
Some things are out on purpose. Kaapi is specialty only, so no chicory and no degree coffee, and a South Indian filter is just another brewer. That gives up the biggest coffee audience in the country in exchange for a coherent identity, which was a deliberate trade.
How it works
Brew Notes was a Vite single-page app talking to Supabase from the browser. That suited a private journal, and it's wrong for a catalogue whose acquisition channel is search. A crawler sees an empty shell, and link scrapers on WhatsApp or Slack never run JavaScript, so per-page share images are impossible. Kaapi is Next.js on the App Router with Supabase behind it, and the catalogue pages are static HTML that revalidates hourly. Astro would have made leaner pages but fought the logged-in half of the product. TanStack Start was the natural continuation of Brew Notes and lost on maturity.
Ingestion is the interesting part:
Five roasters run Shopify and expose /products.json. Three run WooCommerce, whose public Store API needs no auth either. Two base adapters cover all eight. The fetch and the normalisation are separate steps on purpose. Raw responses get stored and committed, and normalisation runs over the stored copy, so changing a rule never means hitting every roaster again. A diff between last week's raw payload and this week's is also the alarm when a roaster changes its schema. Subko went from 63 products to 59 within a single day, which is the argument for keeping raw copies in one number.
The whole catalogue ships to the browser as one payload and filters there. At 145 coffees that's safe by two orders of magnitude, and it means filtering on a phone never waits on a network round trip.
What got hard along the way
Telling a coffee from a t-shirt turned out to be the first real problem. Shopify's product_type field is useless for it. Subko leaves it empty on 56 of 59 products, and Blue Tokai types a blend called French Roast as "Single Estate Coffee." What works is a three-layer rule on the variant options. A grind option is near-certain evidence on its own. Failing that, a size option counts only if its values are weights like "250 g," because hoodies are also sold by size. A title exclusion then removes gift boxes, bundles and chocolate, which come in grams and still aren't coffee. From 447 raw products that leaves 145 coffees.
Prices had their own traps. WooCommerce reports one price and a min/max range rather than a price per variant. The first adapter stamped the minimum on every pack size, which claimed Naivo's 2×250 g pack cost the same ₹745 as the single bag. The fix leaves the price empty when it can't be known. Woo's currency_minor_unit also varies between shops: Halli Berri's "1890" means ₹1890 and Naivo's "74500" means ₹745.
One trap came from the framework. The Supabase SSR client reads cookies, and reading cookies quietly turns a Next route dynamic. The first build came out server-rendered on every request, which would have defeated the whole reason for choosing Next. The catalogue now uses a separate cookie-free client, and a build that reports the index as dynamic counts as a regression.
The biggest gap is one no parser can close. Of 145 coffees, roast level is published for 38 and process for 15. The example query from the founding brief, light-roast washed under ₹800 for a V60, returns zero results today. Inferring a roast level from marketing copy and presenting it as data was ruled out, so this is manual curation of every row.
Where it stands
The backend is built and tested. The design is settled as a quiet index of type and hairlines, with filters written as a sentence. The catalogue index runs against the live database: 145 offerings from eight roasters, with photographs cached in its own storage. Next come offering pages with share cards, estate pages that link one hillside across roasters, the facet curation, and then the logging layer. It isn't public yet, and "Kaapi" is still a working title.