Nosh
| saynosh.com | lukeharwood11/SayNosh.com |
Where your group finally agrees on dinner.
Picking a restaurant with friends usually turns into a group chat spiral — everyone has different cravings, nobody wants to be the one to decide, and "I'm fine with whatever" never actually means whatever. Nosh fixes that by turning dinner into a quick swipe session: one person starts, everyone votes yes / no / meh on nearby spots, and the app surfaces where your group actually overlaps.
How it works
- Create a session — Pick an area (location or address), set filters like radius and cuisine, and Nosh pulls nearby restaurants from the Google Places API.
- Invite your group — Share an invite code or send invites to people you've dined with before. Friends can join as guests or sign in.
- Swipe together — Each person swipes through the same deck of restaurants: yes, no, or neutral ("meh"). Everyone sees who's still swiping in the waiting room.
- Get a winner — When everyone finishes, Nosh scores the votes and reveals a match. If multiple places work, the host can pick the highest-rated option or run a Round 2 to narrow it down.
The host can also skip members who are taking too long, so one slow swiper doesn't hold up the whole group.
Matching algorithm
The scoring logic is deliberately simple and opinionated:
- Any no vote eliminates a restaurant for that round.
- All yes votes → Strong Match.
- A mix of yes and neutral (with no nos) → Soft Match.
- If there are strong matches, soft matches are dropped from the final options.
- No matches at all? Nosh falls back to the highest-rated restaurant with the fewest objections.
If multiple restaurants match, the group can either let Nosh pick the top-rated one or re-swipe only the matches in Round 2.
Friends without friend requests
After a session ends, everyone who participated is saved to each other's known-users list — no friend requests, just shared dinner history. Next time you start a session, you can one-tap invite people you've eaten with before. Guests who join without an account are prompted to sign up so they can be re-invited later.
Tech stack
| Layer | Tech |
|---|---|
| Frontend | Vite, React 19, TypeScript, Tailwind CSS, shadcn/ui |
| State | Zustand + TanStack React Query |
| Backend | Supabase (Postgres, Auth, Realtime, Edge Functions) |
| Restaurant data | Google Places API (New) — cached per session |
| Analytics | PostHog (optional) |
| Hosting | AWS S3 + CloudFront + Route53 via Terraform |
| CI / E2E | GitHub Actions, Playwright |
The repo is a monorepo: web/ for the client, supabase/ for migrations and edge functions, and terraform/ for production hosting. Supabase Edge Functions handle the server-side work — creating sessions, joining, submitting swipes, calculating results, sending invites, and place autocomplete — while the static site ships to S3.
Realtime multiplayer
Sessions use Supabase Realtime so the waiting room updates live as members join, submit their swipes, or get skipped. The swipe deck itself is local-first (votes live in Zustand until submission), but session state syncs across clients so everyone lands on the results screen together.
What I built
Nosh is a full-stack product, not just a UI prototype:
- Session lifecycle — waiting → swiping → round two → completed, with 24-hour session expiry
- Row Level Security on all Postgres tables
- Authenticated edge functions with host-only controls (skip members, force results)
- PWA support via Vite PWA plugin
- E2E test suite with Playwright, including multiplayer specs against a local Supabase stack
- Terraform CI/CD — plan on PR, apply + S3 sync + CloudFront invalidation on merge
Stop arguing. Start swiping.