ShopAI: one grocery search across nine retailers
Search once and see live prices for the same item at every nearby store. Then turn a whole shopping list into a multi-store trip. Search and planning are deterministic: the shipped app makes no language-model calls.
Live on the Apple App Store
Architecture
A React Native / Expo client sits in front of a standalone Express + TypeScript backend, which runs on AWS EC2 behind Nginx. The app itself contains no search logic. Every search, planner request and warm-up check goes to the backend, and all ranking and filtering happens there.
Retailer adapters
None of the nine retailers built their systems for an app like this. Most of the work is making structurally different sources look like one.
| Integration | Retailers |
|---|---|
| REST API, OAuth2 client credentials | Kroger, Harris Teeter |
| Frontend GraphQL, anonymous session cookie | Aldi, Sprouts, Publix (via Instacart) |
| Headless browser (Playwright) | Trader Joe's |
| Server-rendered HTML parsing | Whole Foods |
| Store locations only; no live product source yet | Albertsons, Tom Thumb (reported as unavailable) |
Every adapter emits the same ApiProduct shape (name, size, price, store, rating, image, optional nutrition). Nothing above the adapter layer ever special-cases a store, so adding a retailer means writing one more adapter. A declarative onboarding path for browser-based stores was validated on Trader Joe's and Aldi. Target was evaluated and left out because its terms of service prohibit automated collection.
Search path
- Query correction normalizes typos and plurals once, so every adapter searches the same term.
- Concurrent fan-out. All retailer calls start together (
Promise.all) and race a shared response-time budget, so one slow store comes back aspendinginstead of delaying the other eight. - Filtering and ranking. Each store's raw results pass the same food filter, relevance filter and per-store selection, followed by a cross-store image backfill.
- Explicit status. Each store is reported as
ok,error,unavailableorpending, so the app never has to guess why a section is empty. - Warm-up. OAuth tokens, session cookies and the browser session are set up at boot and per ZIP code by a small state machine (starting → warming → ready), keeping that cost off shoppers' requests.
Planner
The Smart Shopping Planner resolves each list item against live search results and then decides which stores to visit. With seven stores carrying live product data, there are 27 − 1 = 127 non-empty store combinations. That is few enough to score every one exactly, so "cheapest" really is the cheapest of all options rather than the output of a heuristic.
- Cheapest, fastest and fewest stops are single-field sorts: total cost, then drive time, then store count.
- Balanced is the one weighted multi-factor score.
- Gas cost uses a stated approximation: 25 mpg at $3.50 per gallon.
- A "healthiest" mode only scores products with real nutrition data from Open Food Facts. Missing data is treated as missing, not as the worst score.
- A store is excluded only when it is confirmed closed, which currently only Kroger's posted hours can show. Unknown hours never count as closed.
I originally described every plan as using the weighted score. Reviewing the code against my own design notes showed that only "balanced" does, so the write-up was corrected.
Production issues
- A Node.js
setTimeoutdelay overflow in a 30-day location cache stalled the event loop, turning ~1.5-second searches into 27-second ones. I fixed it by capping the sweep interval. - On the previous host, a Playwright browser-path setting was silently ignored because the service predated its config file. This surfaced as a live Chromium failure in production.
Scope and origins
ShopAI is the new name for ShopSmart, a four-person hackathon prototype from August 2025, and it extends ShopSmart’s core idea of finding the cheapest way to shop across stores. Under the ShopSmart name the project won 1st place at the TechLit Bridging the Gap Hackathon (Aug. 2025, Devpost), the Best Innovation Award at PeddieHacks (Aug. 2025, 1st of 250), 3rd place at Hack4Humanity (Oct. 2025, 3rd of 500) and 5th place at AsterHacks (Oct. 2025, 5th of 250). Those were team awards for the prototype. ShopAI is my solo rebuild.
For the App Store release I cut meal planning, an AI assistant, OCR, barcode scanning and several other features behind a single feature-flag file. The code still exists, but it isn't shipped. The architecture write-up covers what was cut and the layered design it's meant to grow into. The app has a small number of App Store ratings, and I don't have usage figures to report.