How to Build a Calorie Counter App with React and ChowAPI
Calorie tracking apps are one of the most popular categories in health and fitness. But building one from scratch means you need reliable food nutrition data, and that is where most developers get stuck.
In this tutorial, you will build a fully functional calorie counter in React that lets users search for foods, view nutrition facts, and track their daily intake. We will use ChowAPI to power the food search and nutrition data, giving us access to over 1.6 million foods with 34 nutrients per item.
By the end, you will have a working app that looks something like this: a search bar at the top, a results list with calorie counts, and a daily log that tallies up your totals.
Prerequisites
Before we start, make sure you have:
- Node.js 18+ and a React project (we will use Vite, but Create React App works too)
- A ChowAPI API key - grab one at chowapi.dev/signup (credit packs from $5/5K calls)
- Basic familiarity with React hooks (
useState,useEffect)
Step 1: Install the ChowAPI SDK
Install the official TypeScript SDK in your project:
npm install chowapi
Then initialize the client. Create a file called lib/chowapi.ts:
import { ChowAPI } from "chowapi";
const chow = new ChowAPI({
apiKey: import.meta.env.VITE_CHOWAPI_KEY,
});
export default chow;
Add your API key to .env.local:
VITE_CHOWAPI_KEY=chow_live_your_key_here
Important: Never expose your live API key in client-side code for production apps. In a real deployment, proxy requests through your own backend. For this tutorial, a test key works fine.
Step 2: Build the Food Search Component
The core of any calorie counter is food search. ChowAPI provides fuzzy, typo-tolerant search out of the box, so users can type "chickn brest" and still get the right results.
Create a FoodSearch.tsx component:
import { useState, useEffect } from "react";
import chow from "../lib/chowapi";
interface FoodResult {
id: string;
name: string;
brand: string | null;
calories: number;
protein: number;
carbs: number;
fat: number;
}
export default function FoodSearch({ onAdd }: { onAdd: (food: FoodResult) => void }) {
const [query, setQuery] = useState("");
const [results, setResults] = useState<FoodResult[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (query.length < 2) {
setResults([]);
return;
}
const timeout = setTimeout(async () => {
setLoading(true);
const { data } = await chow.search(query, { limit: 8 });
setResults(
data.map((food) => ({
id: food.id,
name: food.name,
brand: food.brand_name,
calories: food.calories,
protein: food.protein_g,
carbs: food.carbohydrates_g,
fat: food.total_fat_g,
}))
);
setLoading(false);
}, 300);
return () => clearTimeout(timeout);
}, [query]);
return (
<div>
<input
type="text"
placeholder="Search foods... (e.g., chicken breast, banana)"
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
{loading && <p>Searching...</p>}
<ul>
{results.map((food) => (
<li key={food.id}>
<strong>{food.name}</strong>
{food.brand && <span> — {food.brand}</span>}
<span> | {food.calories} kcal</span>
<button onClick={() => onAdd(food)}>+ Add</button>
</li>
))}
</ul>
</div>
);
}
Notice the 300ms debounce on the search input. This prevents firing an API call on every keystroke, keeping your usage efficient. With ChowAPI's pricing at $0.002 per call (or as low as $0.0006/call with credit packs), a debounced search is extremely cost-effective.
Step 3: Display Nutrition Facts
When a user selects a food, show them the macronutrient breakdown. Create a NutritionCard.tsx component:
interface NutritionCardProps {
name: string;
calories: number;
protein: number;
carbs: number;
fat: number;
}
export default function NutritionCard({ name, calories, protein, carbs, fat }: NutritionCardProps) {
return (
<div className="nutrition-card">
<h3>{name}</h3>
<p className="calories">{calories} kcal</p>
<div className="macros">
<div>
<strong>{protein}g</strong>
<span>Protein</span>
</div>
<div>
<strong>{carbs}g</strong>
<span>Carbs</span>
</div>
<div>
<strong>{fat}g</strong>
<span>Fat</span>
</div>
</div>
</div>
);
}
All ChowAPI nutrition values are reported per 100 grams, which makes math straightforward. If a user logs 150g of chicken breast, multiply each value by 1.5.
Step 4: Track Daily Calorie Totals
Now wire everything together with a daily log. The App.tsx ties the search and tracking into one view:
import { useState } from "react";
import FoodSearch from "./components/FoodSearch";
import NutritionCard from "./components/NutritionCard";
interface LogEntry {
id: string;
name: string;
calories: number;
protein: number;
carbs: number;
fat: number;
}
export default function App() {
const [log, setLog] = useState<LogEntry[]>([]);
const totals = log.reduce(
(acc, entry) => ({
calories: acc.calories + entry.calories,
protein: acc.protein + entry.protein,
carbs: acc.carbs + entry.carbs,
fat: acc.fat + entry.fat,
}),
{ calories: 0, protein: 0, carbs: 0, fat: 0 }
);
return (
<div className="app">
<h1>Calorie Counter</h1>
<div className="daily-totals">
<h2>Today's Totals</h2>
<p><strong>{totals.calories}</strong> kcal</p>
<p>Protein: {totals.protein}g | Carbs: {totals.carbs}g | Fat: {totals.fat}g</p>
</div>
<FoodSearch onAdd={(food) => setLog([...log, food])} />
<div className="food-log">
<h2>Food Log ({log.length} items)</h2>
{log.map((entry, i) => (
<NutritionCard key={i} {...entry} />
))}
</div>
</div>
);
}
This gives you a working calorie counter with live search and running totals. The state lives in memory for now. In a production app, you would persist it to localStorage or a database.
Next Steps
You now have a working calorie counter powered by real nutrition data. Here are some ways to extend it:
- Add serving sizes - let users specify grams or common portions (1 cup, 1 slice) and multiply the per-100g values accordingly
- Persist the log - save daily entries to
localStorageor a backend like Supabase so data survives page refreshes - Barcode scanning - use ChowAPI's barcode endpoint to let users scan packaged foods with their phone camera
- Goal tracking - set daily calorie and macro targets, then show progress bars
- Meal categories - organize entries into breakfast, lunch, dinner, and snacks
ChowAPI credit packs start at $5 for 5,000 calls (50% off PAYG), which is more than enough for a personal tracker or early-stage product. When you are ready to scale, pay-as-you-go pricing is $0.002 per call with no surprise tiers. Or grab the Scale Pack: $50 for 100,000 calls ($0.0005/call, 75% off PAYG).
Ready to start? Get your API key and check the quickstart guide to explore the full API.