Brew Day Companion — Built on Cloudflare
React Native · Cloudflare Workers · D1 · Pages
Nutrient additions at precise times. Gravity readings every few days. Miss a step and you get stuck fermentation or off-flavours. Spreadsheets were not cutting it.
Literally forgot to add nutrients on day 2 because I was watching TV. Built an app with push reminders. Problem solved.
Every existing app wants an account, has analytics SDKs, or runs ads. I wanted one that does nothing you didn't ask for.
Honestly this is also just a good excuse to build something with React Native and mess around with the Cloudflare stack at the same time.
From recipe planning through brew day, fermentation, racking, conditioning, to bottling. Every stage guided and tracked.
Fermentation follows an exponential decay curve. Given a few gravity readings, the app fits the model and tells you when you'll hit target FG.
/recipes
/yeasts
/equipment
/share
/ratings
/api/hydrometer/ispindel
Admin token via
wrangler secret.
Cloudflare Turnstile for bot
protection. CORS locked to the app
domain.
Daily at 03:00 UTC — purges expired shared brews from D1 automatically.
9 migrations so far. Versioned SQL
files, applied via
wrangler d1 migrations
apply.
// wrangler.toml (api)
name = "meadmate-api"
main = "src/index.ts"
compatibility_date = "2024-11-01"
compatibility_flags = ["nodejs_compat"]
[[d1_databases]]
binding = "DB"
database_name = "meadmate-db"
database_id = "c6a6bc16-..."
[triggers]
crons = ["0 3 * * *"]
# secrets set via wrangler CLI:
# ADMIN_TOKEN
# TURNSTILE_SECRET
# MOBILE_APP_SECRET
The iSpindel and RAPT Pill are WiFi hydrometers you float in your fermenter. They measure tilt angle, convert to gravity, and POST readings to a URL you configure. Both are integrated.
/api/hydrometer/ispindel
— no auth required, device
auto-registers by hardware ID
corr-gravity
field) is preferred over raw when present.
Raw values greater than 2.0 are normalised
from SG×1000 format automatically.
// Device POSTs:
{
"name": "MySpindel",
"ID": "abc123",
"angle": 35.2,
"temperature": 20.1,
"battery": 4.1,
"gravity": 1.065,
"corr-gravity": 1.063
}
no I/O/1/0)
ABCDEFGHJKMNPQRSTUVWXYZ23456789// Short code generator
const chars =
'ABCDEFGHJKMNPQRSTUVWXYZ23456789';
const bytes = new Uint8Array(8);
crypto.getRandomValues(bytes);
return Array.from(bytes,
b => chars[b % chars.length]
).join('');
// Stored in D1:
// code, data (JSON), brewer,
// created_at, expires_at,
// view_count, device_id
Static HTML. No build step. Three lines of config. Global CDN, auto TLS, preview deployments on every branch.
name,
compatibility_date,
pages_build_output_dir = ".".
That's the entire config.
X-Frame-Options, X-Content-Type-Options, cache control for screenshots. Declarative, no server needed.
Short URL to Play Store. Works like Netlify redirects - just a text file.
Full lifecycle, 20+ calculators, TOSNA, gravity prediction
Hono + D1, recipes, sharing, ratings, iSpindel, cron cleanup
Auto gravity logging from WiFi hydrometer hardware
Privacy concern — Google requires your legal name and address on the listing.
Static JSON on Pages, Git-backed, PR-contributed
RN codebase is already cross-platform, mostly just testing
meadmate.app · meadmate@perpetualprototypes.io