Barcode Lookup via a Cloud Function

Food Tracker (React + Firebase)

Chapter 3 · Barcode Lookup via a Cloud Function

This is the first piece of this course's own backend code — and the honest first question worth asking is whether it needs to exist at all.

Does This Actually Need a Cloud Function?

Open Food Facts is free, public, and requires no API key — so, strictly speaking, the React app could call it directly with fetch() from the browser, no Cloud Function involved. Chapter 1's own warn-box justified Cloud Functions by "a genuine secret that must never reach the browser" — and there's no secret here at all. So why route it through one anyway? Three reasons that have nothing to do with secrecy:

  • Caching. Many different users of this app will scan the same common products — the same barcode gets looked up repeatedly. A server-side cache means Open Food Facts only gets queried once per barcode, ever, no matter how many people scan it.
  • Consistent error handling. A third-party API's own error shapes are the third-party API's own business — normalizing "not found," "malformed barcode," and "service unavailable" into one consistent response shape is easier to do in one place than in every component that might trigger a lookup.
  • Future-proofing. If a paid, key-requiring nutrition database ever replaces or supplements Open Food Facts, the swap happens entirely inside this one function — zero client-side changes, and the key never has to touch the browser at all.

Writing a Callable Cloud Function

Firebase's callable functions (onCall) handle the client-server plumbing — CORS, request/response serialization, and (from Chapter 11 onward) authentication context — automatically, which is why this course uses them rather than a raw HTTP endpoint.

// functions/index.js const { onCall, HttpsError } = require("firebase-functions/v2/https"); const { getFirestore } = require("firebase-admin/firestore"); exports.lookupBarcode = onCall(async (request) => { const barcode = request.data.barcode; if (!barcode) throw new HttpsError("invalid-argument", "barcode is required"); const db = getFirestore(); const cacheRef = db.collection("barcodeCache").doc(barcode); const cached = await cacheRef.get(); if (cached.exists) { return cached.data(); } const res = await fetch(`https://world.openfoodfacts.org/api/v2/product/${barcode}.json`); const data = await res.json(); if (data.status !== 1) { throw new HttpsError("not-found", "No product found for this barcode"); } const product = { name: data.product.product_name || "Unknown item", category: data.product.categories_tags?.[0]?.replace("en:", "") || "uncategorized", }; await cacheRef.set(product); return product; });

Calling It From React

import { getFunctions, httpsCallable } from "firebase/functions"; const functions = getFunctions(); const lookupBarcode = httpsCallable(functions, "lookupBarcode"); async function handleScan(barcode) { try { const result = await lookupBarcode({ barcode }); console.log(result.data); // { name, category } } catch (err) { if (err.code === "functions/not-found") { // fall back to manual entry — see Chapter 5 } } }

The Cache: a Second Collection, With a Different ID Rule

barcodeCache is a second Firestore collection, entirely separate from items — and here, unlike Chapter 2's own items collection, using the barcode itself as the document ID is exactly correct. Chapter 2 rejected barcode-as-ID for items because that collection tracks one document per purchased instance, and the same product can be bought more than once. barcodeCache tracks one document per product, full stop — there's only ever one canonical name/category for a given barcode, so keying the cache by barcode directly is the right modeling decision this time, not a repeat of Chapter 2's mistake.

Not every Cloud Function exists to hide a secret
Chapter 1 introduced Cloud Functions as the answer to "a genuine secret that must never reach the browser." This chapter's own Cloud Function protects no secret at all — Open Food Facts needs no key. It exists for caching, consistency, and future flexibility instead. Both are legitimate reasons to reach for server-side code in an otherwise serverless architecture; secrecy is only one of them.
What the emulator does and doesn't fake
Running this function against the Firebase emulator (set up in Chapter 1) executes your actual function code locally and reads/writes the local Firestore emulator — but the fetch() call to Open Food Facts still goes out over the real network to the real service. Emulating Firebase doesn't mean faking every external HTTP call your function makes.
A Cloud Function is not automatically rate-limited or secure
Since Chapter 1 established that Firebase's client config isn't secret, nothing inherently stops someone outside this app from calling lookupBarcode directly, as often as they like, using nothing but that public config. Wrapping code in a Cloud Function controls where the code runs, not who's allowed to run it — that's a separate concern, handled by App Check or, more relevantly for this app, the Firebase Authentication covered in Chapter 11. Don't mistake "it's a Cloud Function" for "it's protected."

Where This Course Is Headed

The camera-scanning React component that feeds a barcode into this function, direct client writes to Firestore for the add-item flow, Security Rules as the real gatekeeper, expiry alerts, item history and search, marking items used, recipe lookup (a second Cloud Function, reusing everything from this chapter), Firebase Authentication, deployment, and a capstone.

Hands-On Exercises

Exercise 1

Explain why routing the Open Food Facts lookup through a Cloud Function isn't strictly required, unlike Chapter 1's own justification for Cloud Functions. Name the three reasons this chapter gives for using one anyway.

📄 View solution
Exercise 2

Chapter 2 said never use the barcode as the document ID for the items collection. This chapter uses the barcode as the document ID for barcodeCache. Explain what's different about the two collections that makes both of these the correct decision.

📄 View solution
Exercise 3

Explain the warn-box's distinction between "where code runs" and "who's allowed to run it." Why doesn't putting the barcode lookup in a Cloud Function automatically prevent abuse, and what will actually address that later in the course?

📄 View solution

Chapter 3 Quick Reference

  • Cloud Function reasons, this time — caching, consistent error handling, future-proofing (not secrecy, unlike Chapter 1's own justification)
  • onCall — Firebase's callable-function pattern; handles CORS and (later) auth context automatically
  • barcodeCache — a second collection, correctly keyed by barcode (one document per product, not per purchase)
  • Emulators fake Firebase, not the outside world — a local function still makes real HTTP calls to real third-party APIs
  • A Cloud Function ≠ automatically secure — it controls where code runs, not who can call it; that's Chapter 11's job
  • Next chapter: Camera-Based Barcode Scanning in React