Barcode Lookup: Integrating Open Food Facts
Food Tracker (React + Express + Prisma)
Chapter 3 · Barcode Lookup: Integrating Open Food Facts
The same integration as the sibling course, for the same reasons — a server-side proxy to Open Food Facts, not a direct call from React. What changes here is the cache itself: a Prisma model instead of a raw SQL table, and a genuinely safer write pattern than the sibling's own check-then-insert.
Why Proxy Through the Server At All
Open Food Facts needs no API key, so this isn't about hiding a secret. The real reasons are unchanged from the sibling course:
- Caching. The same barcode gets scanned repeatedly over time — caching the result server-side avoids re-querying Open Food Facts for a product this app already knows about.
- Consistency. Every client gets identical lookup behavior, defined in one place.
- Future-proofing. If Open Food Facts' own API shape changes, or a second data source gets added later, only the server needs to change.
Extending the Schema: A Barcode Cache
A second model in schema.prisma, reusing Chapter 2's own field-naming convention:
barcode is the primary key here — deliberately different from the Item model, where the same barcode can legitimately appear on many separate rows (many separate purchases of the same product over time). One barcode maps to exactly one cached product lookup, but potentially many pantry items.
The Lookup Route
The check-first-then-fetch shape is identical to the sibling course's own route. The write at the end is where this course diverges: upsert() instead of a plain insert.
| Sibling course | This course | |
|---|---|---|
| Cache check | db.prepare(...).get(barcode) | prisma.barcodeCache.findUnique(...) |
| Cache write | A plain INSERT, run only after the check above found nothing | upsert() — insert if the barcode doesn't exist, update it if it does |
| Two requests for the same brand-new barcode, near-simultaneously | Both pass the initial check (neither sees a cache hit yet). The first INSERT succeeds; the second violates the barcode primary key and fails. | Both pass the initial check the same way. The first upsert() creates the row; the second upsert() for the same barcode simply updates it instead of failing. |
barcode is declared PRIMARY KEY there too. Prisma's upsert() is built specifically for exactly this "write it if it's missing, otherwise that's fine" situation — a case where reaching for the ORM's own generated method is a genuine improvement over the raw SQL equivalent, not just a different way of writing the same thing.
fetch is a genuine Node.js global as of Node 18, working identically here whether or not Prisma is involved. This has nothing to do with the database layer at all — it's the same fact, reused.
data.status === 1 only means "a product exists for this barcode," not "this product has complete data." The route above already returns null for missing fields (Prisma's String? fields accept that directly) rather than throwing — Chapter 5's own add-item form has to be built expecting that, exactly as in the sibling course.
Where This Course Is Headed
The camera-scanning React component next — reused essentially unchanged from the sibling course, since decoding a barcode client-side has nothing to do with which backend, or which database layer, receives it afterward.
Hands-On Exercises
Explain the three reasons this chapter gives for proxying the Open Food Facts lookup through Express rather than calling it directly from React, given that no API key is involved.
📄 View solutionUsing this chapter's own compare-table, explain the exact race condition the sibling course's check-then-insert pattern can hit, and why upsert() doesn't have the same problem.
📄 View solutionExplain why barcode is the @id (primary key) on BarcodeCache but not on Item, even though both models have a barcode field.
📄 View solutionChapter 3 Quick Reference
- Why proxy: caching, consistency across clients, future-proofing — not secrecy (no API key needed)
- New model:
BarcodeCache, keyed bybarcode— one lookup per barcode, unlikeItem - Route:
GET /api/lookup/:barcode— checks the cache first viafindUnique(), else calls Open Food Facts and saves the result - Real Prisma win:
upsert()closes a genuine race condition the sibling's own check-then-insert pattern has for a brand-new barcode requested twice at once - Node's built-in fetch: no axios/node-fetch dependency needed since Node 18 — unrelated to the database layer
- Real gotcha, unchanged: Open Food Facts data is crowdsourced and often incomplete — null fields are expected, not an error
- Next chapter: Camera-Based Barcode Scanning in React