09Mobile2026Solo — the app, and the backend it shares with the web
Cook Galaxy — Mobile App
The Expo app for Cook Galaxy: the same backend, the same account, in your hand at the stove.
Private repository

The problem
Cook Galaxy is used in a kitchen, and nobody carries a laptop to the stove. But shipping a mobile app usually means a second product: a second API, a second set of business rules, and two codebases that drift apart until a feature exists in one and not the other. I wanted the app to be a second client, not a second product.
Decisions
01
The backend did not get a mobile branch
Every route accepts either the web session cookie or a mobile bearer token, resolved by the same helper. Nothing is implemented twice, and a feature shipped on the web is available in the app as soon as the screen exists — there is no 'mobile support' ticket per endpoint.
02
The expensive work stays on the server
Photo and link import send the image or URL to the API and get a parsed recipe back. The phone never talks to the AI provider, which keeps the key server-side, the cost gated by the same daily limits as the web, and the app small.
03
Every screen has a state for when things go wrong
All the list screens have a loading state, an empty state and an error state with a retry — plus pull-to-refresh. Half of them did not, and adding them was a deliberate pass rather than something bolted on after a bug report.
04
The upload that broke on an SDK upgrade
Expo SDK 57 routes global fetch through its own implementation, whose FormData converter rejects the classic React Native upload descriptor — the one every tutorial still shows. Photo upload broke silently on upgrade; the fix was to hand it a real file object from expo-file-system instead. The kind of failure you only find by running the thing on a device.
05
Not every web interaction survives the port
Reordering ingredients is drag-and-drop on the web. On a phone, inside a scrolling form, drag-and-drop fights the scroll and loses — so it became up and down buttons. The same feature, a different gesture, because the constraint is different.
What I measured
26
screens
every one with a test, including its error and retry states
743
tests
across 78 suites
1
backend
shared with the web app, route for route
0
duplicated business rules
limits, trials and subscriptions resolve server-side




Built with
- Expo
- React Native
- TypeScript
- RevenueCat
- Jest
