DietlyAPI developer guide. Last reviewed: July 2026

Build a calorie-tracker web app in a weekend

A useful calorie tracker needs three things: a food search, a daily list, and an explainable per-serving calculation. You can build all three as a single client-side page backed by DietlyAPI and the browser's local storage, with no server and no build step. This guide walks the whole app end to end, then shows where to grow it once people actually use it.

What you are building

One HTML file. A search box calls GET /search as the user types. Tapping a result adds it to today's log with an editable gram amount. A running total sums every entry using the same math. Everything persists in localStorage, so a refresh keeps the day. That is a genuinely usable tracker, and it never exposes a privileged key because the read endpoint is public.

1. Search foods as the user types

Wait for at least two characters (the API's minimum for q), debounce so you are not firing a request per keystroke, then render name, brand and the per-100 g calories. Remember the response is a JSON array.

const API = 'https://api.getdietly.com';
let timer;

function onType(e) {
  clearTimeout(timer);
  const query = e.target.value.trim();
  if (query.length < 2) return;
  timer = setTimeout(() => runSearch(query), 250);
}

async function runSearch(query) {
  const url = new URL(API + '/search');
  url.search = new URLSearchParams({ q: query, limit: '8' });
  const res = await fetch(url);
  if (!res.ok) { showError(res.status); return; }
  const foods = await res.json();
  render(foods); // foods is [] when nothing matched
}

2. Store log entries, not search results

When a person adds a food, keep the ID, a display name, the grams, and a snapshot of the per-100 g calories used in the calculation. The snapshot keeps a past day readable even if the catalog record later changes, and you can always re-fetch the live record with GET /food/{id} when editing.

function addFood(food) {
  const entries = load();
  entries.push({
    id: food.id,
    name: food.name,
    grams: 100,
    kcal100: food.calories_kcal,   // per 100 g snapshot
    protein100: food.protein_g
  });
  save(entries);
  draw();
}

const KEY = 'dietly-log';
const load = () => JSON.parse(localStorage.getItem(KEY) || '[]');
const save = (e) => localStorage.setItem(KEY, JSON.stringify(e));

3. Make totals inspectable

Sum every entry with one grams-to-100 g formula, round only for display, and let the total open into the entries that produced it. A missing nutrient is unavailable, not zero, so skip nulls in the sum instead of coercing them.

function totals(entries) {
  return entries.reduce((acc, e) => {
    if (e.kcal100 != null) acc.kcal += e.kcal100 * e.grams / 100;
    if (e.protein100 != null) acc.protein += e.protein100 * e.grams / 100;
    return acc;
  }, { kcal: 0, protein: 0 });
}

function draw() {
  const entries = load();
  const t = totals(entries);
  document.querySelector('#total').textContent =
    `${Math.round(t.kcal)} kcal, ${Math.round(t.protein)} g protein`;
}

4. Let people edit grams without a new search

Editing the gram amount is the single most-used action in any tracker. Change it in place and redraw. No network call is needed because you already stored the per-100 g snapshot.

function setGrams(index, grams) {
  const entries = load();
  entries[index].grams = Math.max(0, Number(grams) || 0);
  save(entries);
  draw();
}

5. Handle the ordinary failure states

6. Wire up the page and ship it

The whole app is one static file: a search input, a results list, and a log with a total. Because there is no build step and no server, you can host it free on any static host (GitHub Pages, Cloudflare Pages, Netlify) by uploading a single index.html. The markup below is all the structure the functions above need.

<input id="q" oninput="onType(event)" placeholder="Search a food">
<ul id="results"></ul>
<h2>Today</h2>
<ul id="log"></ul>
<p id="total">0 kcal</p>
<script src="app.js"></script>

Render search results as buttons that call addFood, and render the log with a number input bound to setGrams. Call draw() once on load so a returning visitor sees yesterday's total immediately. Keep every DOM update in one draw function; a single render path is far easier to reason about than scattered updates, and it makes the total and the list impossible to get out of sync.

One accessibility note worth the two minutes: give the search input a real <label>, mark the results list as a live region so screen readers announce matches, and keep focus on the input after adding a food so fast entry keeps working. None of this costs performance, and it makes the tracker usable one-handed on a phone, which is where most people log meals.

Where to grow it next

The prototype above is deliberately keyless and local. When you need a shared log across devices, add accounts and server-side storage on your own backend, and move any account-level Dietly calls behind that server so a privileged key never ships in the browser bundle. At that point, create a free key in the dashboard, cache popular lookups, and set target calories with the separate calorie calculator rather than reimplementing goal math yourself.

Build contract: use the OpenAPI specification for exact fields, the integration guide for rate limits and attribution, and treat every nutrient as possibly null.