Nutrition data
Noms stores one number per nutrient, per 100 of the food: per 100 grams for a solid, per 100 millilitres for a drink. Separately, it tells you the sizes people measure that food in. Every figure you put on screen is those two multiplied together.
This page is that multiplication, start to finish.
The idea, in plain words
Say you fetch a snack bar. Three things come back with it:
basis_unit grams → every value below is per 100 g
PROTEIN 10 g → 10 g of protein in every 100 g
one serving 1 bar, 50 g → a portion is 50 g
Read it as a single sentence:
There are 10 g of protein in every 100 g, and one bar is 50 g.
A bar is half of 100 g, so it holds half the protein: 5 g. Two bars, 10 g. Three, 15 g. Half a bar, 2.5 g.
There is no "protein per bar" field to look up, and there does not need to be one. You scale the per-100 figure by the portion you care about, and that same multiplication gives you every nutrient at every portion size. It is how a handful of stored numbers rebuilds any nutrition label you want to print.
The formula
The formula
amount in a serving = nutrient.value × serving[food.basis_unit] / 100
basis_unit is doing two jobs at once, and it is the one field worth slowing down
for. It tells you what the values are per (grams or milliliters), and it
names the serving field to divide by:
basis_unit | Values are per | Divide by the serving's |
|---|---|---|
"grams" | 100 g | grams |
"milliliters" | 100 ml | milliliters |
The field it names is never null, so that lookup always resolves.
A solid, in JSON
The same snack bar, as the API returns it, trimmed here to the three fields this page is about:
{ "basis_unit": "grams", // every value below is per 100 g "nutrients": [ { "id": "ENERGY", "name": "Energy", "value": "400", "unit": "kcal" }, { "id": "PROTEIN", "name": "Protein", "value": "10", "unit": "g" } ], "serving_sizes": [ { "unit": "each", "descriptor": "bar", "quantity": "1", // one bar... "grams": "50", // ...and one bar weighs 50 g "milliliters": null, // no volume; we don't guess one "is_default": true } ] }
basis_unit is "grams", so divide by the serving's grams, which is 50:
protein per bar = 10 × 50 / 100 = 5 g
energy per bar = 400 × 50 / 100 = 200 kcal
A drink, the same shape
Nothing about the method changes for something measured by volume. Only which field you read does:
{ "basis_unit": "milliliters", // every value below is per 100 ml "nutrients": [ { "id": "ENERGY", "name": "Energy", "value": "60", "unit": "kcal" }, { "id": "PROTEIN", "name": "Protein", "value": "3", "unit": "g" } ], "serving_sizes": [ { "unit": "ml", "descriptor": null, "quantity": "250", // 250 ml... "grams": null, // ...of unstated weight; density is not ours to guess "milliliters": "250", // ...which is, of course, 250 ml "is_default": true } ] }
basis_unit is "milliliters", so divide by the serving's milliliters:
protein per bottle = 3 × 250 / 100 = 7.5 g
energy per bottle = 60 × 250 / 100 = 150 kcal
Notice grams is null on that serving. Turning a volume into a weight needs the
food's density, which we will not guess, so the dimension a food is not
anchored on is best-effort and may be missing. The one basis_unit names never is.
One function, both cases
Because the unit names the field, you never branch on it:
function amountPerServing(food, nutrient, serving) { const basis = Number(serving[food.basis_unit]); // "grams" or "milliliters" return (Number(nutrient.value) * basis) / 100; } const serving = food.serving_sizes.find((s) => s.is_default); const protein = food.nutrients.find((n) => n.id === "PROTEIN"); amountPerServing(food, protein, serving); // 5
Reading a serving
serving_sizes is a list. Each entry is one way people measure that food: a
weight, a volume, or a countable piece:
| Field | What it is |
|---|---|
unit | The measure: g, ml, cup, tbsp, tsp, L, oz, fl_oz or each |
quantity | How many units make this serving: 1 bar, 250 ml |
descriptor | Names the portion when the unit alone is not enough (bar, slice, chopped); null otherwise |
grams | What this serving weighs, when known |
milliliters | What this serving measures by volume, when known |
is_default | The food's primary serving. Exactly one per food |
Read the portion off these fields, never off the food's name. If you show a single
figure, show the serving with is_default: true. That is the reference portion
the source itself treats as one serving.
Two things to watch
Decimals arrive as strings
value, quantity, grams and milliliters are JSON strings, not numbers,
so nothing is lost to floating point in transit. Parse them with a decimal type
where the arithmetic matters (Decimal in Python, BigNumber or similar in
JavaScript) and round only at the moment you display. Real catalog values carry
more decimal places than the tidy figures above, which is exactly why.
Each nutrient also carries its own unit (g, mg, kcal, ...), and that is
independent of the food's basis_unit. basis_unit says what the value is per;
nutrient.unit says what the value is measured in. Protein at "10" with
unit: "g" on a grams-basis food reads: 10 grams of protein per 100 grams of
food.
See a whole response
Everything above, on a real catalog row: Blue Diamond's roasted salted almonds, with the nutrient array as long as it actually is:
curl -G https://api.noms.sh/v1/foods/TIgbNPnzCIjX \ --data-urlencode "include=nutrients,serving_sizes" \ -H "Authorization: Bearer $NOMS_KEY"
Same three fields, same one multiplication. 21.428571 × 28 / 100, rounded for
display, is the 6 g of protein printed on the can. So is every other figure on
its panel.
Next steps
- Authentication: get a key and send it on every request.
- Querying & data model: how to search, filter, sort and shape.