< back to work > case_study

> · project

Przepisnik

A recipe book for home bakers — same code, three places

Cross-platform recipe and cake-planning app. Turborepo + Bun monorepo sharing business logic between an Expo mobile app and a Vite web app, with Firebase as the backend (REST, no SDK).

Stack

  • Turborepo
  • Bun
  • React Native
  • Expo
  • Tamagui
  • React 19
  • Vite
  • TailwindCSS
  • shadcn/ui
  • TanStack Query
  • Firebase REST

Engagement

  • statusside project
  • modelside project
  • since2024
  • > solo, evenings and weekends

Przepisnik (“recipe book” in Polish) is a recipe-management app for home bakers. Save recipes, plan multi-tier cakes, scale ingredients, walk through bakes step by step. iOS, Android, and web — all driven by the same TypeScript code.

The monorepo

Turborepo + Bun workspaces, two apps and four packages.

apps/
├─ mobile/   ── Expo + React Native + Tamagui ─┐
└─ web/      ── Vite + React 19 + Tailwind ────┤

packages/                                      │
├─ shared/        ── types · hooks · logic ◀───┤
├─ auth/          ── Firebase REST client  ◀───┤
├─ icons/         ── codegen'd SVGs        ◀───┤
└─ category-icon/ ── picker UI             ◀───┘

The split is deliberate. Anything that doesn’t render — recipes, ingredients, cake planner, ingredient parser, shopping list, query orchestration — lives in packages/shared and gets imported by both apps. Anything platform-specific (Tamagui on mobile, Tailwind on web) stays in the app it belongs to.

~/przepisnik

$ bun run stats

  • platforms shipped from one repo 3 (ios / android / web)
  • shared business-logic loc 6 000
  • generated icon components 501
~/przepisnik/cross-platform
TODO: same recipe rendered on mobile (Tamagui) and web (Tailwind) — shared logic, split rendering

Firebase, but REST

Native Firebase SDKs and Expo don’t always get along — every upgrade is an opportunity for the build to break in a creative new way. The whole thing talks to Firebase via the REST API (Identity Toolkit + Realtime Database + SecureToken). Slightly more code to write, far less misery to maintain.

Tokens go into Expo SecureStore on mobile, browser storage on web; the auth context lives in packages/auth and is identical on both platforms.

The baker-specific bits

  • Recipes have nested steps with per-step ingredients, so “preheat the oven” and “add 200g of butter” are first-class objects, not lines in a paragraph
  • useIngredientParser turns “200g flour” or “1 1/2 tsp baking soda” into structured { amount, unit, name } so scaling actually works
  • useCakePlannerCore handles tier sizing, portion math, and ingredient roll-up across multi-recipe bakes
~/przepisnik/cake-planner
TODO: cake planner — multi-tier ingredient roll-up, the most distinctive feature

Offline + persistence

TanStack Query + persistQueryClient keep recipes available when you’re in a kitchen with bad signal — which, if you’ve ever tried to bake from a phone, is most of the time.