Snagbite turns Instagram / social cooking Reels into rich, structured recipes. A user shares a Reel, the backend scrapes the video, extracts frames, and sends them to Google Gemini (structured JSON output) to produce a detailed, culinary-correct recipe that the app renders as an interactive cooking guide.
This is an npm-workspaces monorepo:
| Workspace | Stack | Role |
|---|---|---|
backend/ |
Express + TypeScript | API-only service: recipe extraction, jobs, auth, billing sync, admin. Deployed on Railway (Docker). |
frontend/ |
React 19 + Vite + TypeScript + Capacitor | Native Android app (Capacitor). Shipped via the Google Play Store. |
The canonical, in-depth architecture/context document is
AGENTS.md. Supplementary docs live indocs/(styleguide, scaling plan, provider contracts).
- Submit — the app sends a Reel URL to
POST /api/extract-recipe; the backend creates apendingjob in Supabase and returns ajobId. - Scrape — a background worker resolves the video through a provider chain
(RapidAPI → local
yt-dlp→ first-party Apify actor as fallback). - Frames — the downloaded media is processed with ffmpeg to extract representative frames.
- AI — frames + caption are sent to Google Gemini with a strict JSON schema to produce the structured recipe.
- Poll — the client polls
GET /api/jobs/:idfor status changes and the final recipe.
Data (jobs, recipes, collections, favorites, users) is stored in Supabase Postgres with RLS;
auth is verified via Supabase JWKS (jose); premium entitlements sync via RevenueCat.
The backend can run as ROLE=web, ROLE=worker, or ROLE=both (see backend/.env.example).
- Node.js v20+
ffmpeg+python3available locally (for frame extraction /yt-dlp) — the Docker image installs these automatically.- Accounts/keys: RapidAPI, Google Gemini, Supabase, (optional) Apify, RevenueCat.
npm install# Backend
cp backend/.env.example backend/.env # fill in RapidAPI / Gemini / Supabase / RevenueCat keys
# Frontend (Vite)
# VITE_SUPABASE_URL, VITE_SUPABASE_PUBLISHABLE_KEY,
# VITE_REVENUECAT_ANDROID_API_KEY, VITE_API_BASE_URLRuns backend (tsx watch) and frontend (vite) together:
npm run devThe Vite dev server proxies /api to the backend on localhost:3000.
For the develop-on-the-go → test on your phone → merge loop, the frontend can be built as a
static web preview against a shared dev backend + dev Supabase, and a
test-user auto-login (VITE_TEST_LOGIN=true) skips the login screen so the authenticated UI
renders in a mobile browser. Quick local run:
# fill frontend/.env.development.local with the dev Supabase + test-user creds (git-ignored)
npm run dev -w frontend # auto-logs-in as the test user
# provision the test user once (from backend/):
SEED_TEST_USER_PASSWORD=… npm run seed:dev -w backendFull setup (self-hosted Supabase on Railway, per-PR previews, user provisioning) is documented in
docs/dev-environment.md.
From frontend/:
npm run cap:sync # build web assets + sync into the Android project
npm run cap:run # build, sync, and run on a device/emulator
npm run cap:live # wireless/USB live-reload (local frontend + local backend)
npm run cap:live:cloud # live-reload pointing to Railway cloud dev backendRelease + Play Store upload are automated via the PowerShell scripts in frontend/scripts/
(release.ps1, deploy-playstore.ps1) and orchestrated from the repo root by deploy.ps1:
npm run deploy # interactive: backend and/or app
npm run deploy:all:internal # bump + backend deploy + app to Play internal track- Backend → Railway. Railway builds the root
Dockerfile(multi-stage,node:22-alpine, ffmpeg/python3 at runtime). Deploy is triggered bydeploy.ps1mergingdevelop → masterand pushing tags. The container is API-only — it does not serve a web frontend. - App → Google Play.
frontend/scripts/deploy-playstore.ps1builds a signed AAB and uploads it to the configured track.
- Endpoint:
POST /api/extract-recipe - Body:
{ "url": "https://www.instagram.com/reel/…/" } - Response (202):
{ "success": true, "jobId": "q8z46p9u8", "status": "pending" }
- Endpoint:
GET /api/jobs/:id - Response (200):
{ "success": true, "job": { "id": "q8z46p9u8", "url": "https://www.instagram.com/reel/…/", "status": "completed", "recipe": { "title": "Creamy Tuscan Chicken Pasta", "servings": 4, "ingredients": [ { "name": "chicken breast", "amount": 2, "unit": "pieces", "notes": "sliced" } ], "instructions": [ { "step": 1, "description": "Boil the pasta in salted water until al dente." } ] } } }
See backend/src/routes.ts for the full endpoint list (jobs, remix, chat, collections,
favorites, extraction limits, billing sync, feedback, admin, push-notification tokens).
A GET /health endpoint reports DB connectivity and role.
The worker can generate personalized, context-aware push notifications from a user's saved
cookbook (season, weekday/time, save-age, favorites, tags) and deliver them via FCM, with
frequency capping and opt-in categories. Ships off (NOTIFICATIONS_ENABLED=false); see
docs/push-notifications.md for the design, notification-type
catalog, and how to enable it (Firebase project + google-services.json + a new AAB release).