๐ก We check www.themealdb.com (and 70+ other public dev APIs) with a plain GET every 30 minutes โ see the live status page. As of publishing it's up and answering fast.
TheMealDB is the recipe API behind nearly every recipe-app tutorial: real dishes, real photos, a free test key (1) right in the URL, CORS open. It's a well-run community project and โ unlike half the APIs on our status board โ it's alive and fast. If you're building a recipe UI to learn fetch, it's genuinely good teaching material.
But every recipe-app tutorial eventually hits the same four walls (all checked live on September 16, 2026):
{"meals":null} โ not an empty array. Search with no match, or filter by a category that doesn't exist, and the API answers 200 {"meals":null}. The next line of every tutorial is data.meals.map(โฆ) โ TypeError: Cannot read properties of null (reading 'map'). This single quirk powers years of Stack Overflow questions.1 is for development or educational use, and "you must become a supporter if releasing publicly on an appstore". The URL that worked all through the tutorial isn't licensed for the launch.POST answers a 404 HTML page. Adding your own meals is a supporter feature. Your "submit a recipe" form has nowhere to go.latest.php, and randomselection.php as premium. (Fun detail: randomselection.php does answer on the test key โ with exactly one meal instead of the ten supporters get.)The fix that keeps your rendering code: copy real records into a free hosted mock API you fully control โ same field names (strMeal, strMealThumb, even strIngredient1โฆstrIngredient20), plus substring search, writes that persist, and an exact {"meals":[โฆ]} response shape. No signup, no key.
# 1. pull a batch from TheMealDB and wrap it as a db.json
curl "https://www.themealdb.com/api/json/v1/1/search.php?s=chicken" \
| python3 -c "import sys,json; print(json.dumps({'meals': json.load(sys.stdin)['meals']}))" \
> db.json
# 2. import it โ the response includes your project id + admin key
curl -X POST "https://mockbird.mockbird.workers.dev/api/projects/import?name=recipes" \
-H 'content-type: application/json' --data-binary @db.json
That hosted 25 meals for us, verbatim: every key per record (54 when we ran it โ TheMealDB has added and removed fields over time, and the import doesn't care) โ strInstructions intact, strMealThumb photo URLs untouched, strTags nulls preserved, and idMeal still the string "52795" (so tutorial code doing strict comparisons keeps working). Your ingredient loop over strIngredient1โstrIngredient20 runs unchanged.
| TheMealDB | Your Mockbird project |
|---|---|
search.php?s=curry | /meals?strMeal_like=curry โ same case-insensitive substring semantics |
lookup.php?i=52795 | /meals/4 โ or filter ?idMeal=52795 |
filter.php?c=Seafood | /meals?strCategory=Seafood |
filter.php?a=Japanese | /meals?strArea=Japanese |
| filter's slim shape | ?select=strMeal,strMealThumb,idMeal |
| โ (no equivalent) | ?q=coconut โ full-text across every field, instructions included |
| 100-item listing cap | ?page=2&limit=10 + X-Total-Count header |
random.php | no random endpoint โ pick a random id client-side (honest gap) |
curl "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/meals?strMeal_like=curry&select=strMeal"
# โ [{"id":17,"strMeal":"Katsu Chicken curry"},{"id":18,"strMeal":"Nutty Chicken Curry"}]
curl "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/meals?strCategory=Chicken&limit=3&select=strMeal,strMealThumb,idMeal"
Tutorial code reads data.meals. One query param reproduces that envelope exactly:
curl "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/meals?mock_envelope=%7B%22meals%22%3A%22%24data%22%7D&strMeal_like=congee&select=strMeal,strArea"
# โ {"meals":[{"id":4,"strMeal":"Chicken Congee","strArea":"Chinese"}]}
# no match? you get {"meals":[]} โ an ARRAY. .map() renders an empty list
# instead of throwing. That's the null trap from the intro, gone.
Set it once as the project default (PUT /api/projects/:id/settings {"envelope":โฆ}) and every GET answers in TheMealDB's shape โ the rest of your fetch code doesn't change at all. Details in docs โ envelope.
curl -X POST https://mockbird.mockbird.workers.dev/m/PROJECT_ID/meals \
-H 'content-type: application/json' \
-d '{"strMeal":"Family Ragu","strCategory":"Pasta","strArea":"Italian",
"strInstructions":"Simmer slowly for three hours."}'
# โ 201 {"id":26,โฆ} โ and GET /meals/26 returns it. It persists.
That's the "submit a recipe" feature TheMealDB reserves for supporters โ and it turns the standard read-only recipe grid into full CRUD: an add-recipe form, an edit page, a delete button. A portfolio piece instead of a fetch demo.
# rate-limited twice, then success โ exercise your retry logic
curl "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/meals?mock_seq=429,429,200"
# slow response for your skeleton/loading state
curl "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/meals?strMeal_like=curry&mock_delay=2000"
More recipes in testing loading & error states.
Credit where due: TheMealDB's content is the product โ hundreds of real recipes with professional photos, categories, areas, and YouTube links, maintained for years and still answering our checks in well under a second. We host your copy of some records; we don't have their catalogue, and your import is a snapshot, not a live mirror. If you're learning fetch in dev, their test key is fine and the project deserves support โ becoming a supporter is a fair deal if you ship a real recipe app on their data. The same goes for its sibling TheCocktailDB (same operator, same key model โ its traps are sharper, though: see our TheCocktailDB guide). The honest split: real recipe content and photos โ TheMealDB. Your own records with search, writes, exact-shape envelopes, pagination past 100, and failure drills โ licensed for whatever you ship โ your import.
The two-curl block in section 1, or zero setup at all:
curl https://mockbird.mockbird.workers.dev/m/demo/products?limit=3
Facts checked live on September 16, 2026: search.php?s=xyzzynotameal and filter.php?c=NopeCategory both answer 200 {"meals":null}; the api.php docs page states the test key 1 is for development or educational use, that becoming a supporter is required "if releasing publicly on an appstore", that supporters can add their own meals and "list the full database rather than limited to 100 items", and marks multi-ingredient filter / latest.php / randomselection.php "*Premium API Only"; POST to the API answers a 404 HTML page; v2/1/randomselection.php on the test key returned exactly 1 meal. Every Mockbird command on this page was run against a live project before publishing.
Full API reference in the docs. More guides: FakeStoreAPI alternative ยท DummyJSON alternative ยท Mock API for React ยท Host a JSON file as an API ยท Universities API alternative. Create your API โ