π‘ We check www.thecocktaildb.com (and 75+ other public dev APIs) with a plain GET every 30 minutes β see the live status page. As of publishing it's up and answering in under half a second.
TheCocktailDB is the drinks API behind nearly every cocktail-app tutorial: real recipes with photos, a free test key (1) right in the URL, CORS open, run by the same folks as TheMealDB. It's alive, fast, and β rare in this space β even publishes an AGENTS.md for AI tools. Genuinely good teaching material.
But cocktail-app tutorials hit walls that are sharper than TheMealDB's (all checked live on September 16, 2026):
200 {"drinks":null} (so data.drinks.map(β¦) throws TypeError: Cannot read properties of null). A filter with no hits answers 200 {"drinks":"no data found"} β a string, so data.drinks.length is happily 13 and .map() throws a different error. A bad-id lookup is null again. Your error handling needs three branches for one API.filter.php?i=vodka, ?i=gin, ?i=rum, ?i=tequila β each answered a single result when we checked. It isn't in the docs; your "browse by ingredient" grid renders one card and looks broken. The same one-item truncation applies to popular.php, latest.php, and randomselection.php (all marked Premium).1 is for development or educational use, and you must buy the Premium API key if releasing publicly on an app store. Multi-ingredient filtering and full-database listings are Premium too; free listings cap at 100 items.The fix that keeps your rendering code: copy real records into a free hosted mock API you fully control β same field names (strDrink, strGlass, even strIngredient1β¦strIngredient15), plus substring search, ingredient search that returns everything, writes that persist, and one consistent {"drinks":[β¦]} response shape. No signup, no key.
# 1. pull a batch from TheCocktailDB and wrap it as a db.json
curl "https://www.thecocktaildb.com/api/json/v1/1/search.php?s=margarita" \
| python3 -c "import sys,json; print(json.dumps({'drinks': json.load(sys.stdin)['drinks']}))" \
> 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=cocktails" \
-H 'content-type: application/json' --data-binary @db.json
That hosted 6 margaritas for us, verbatim and with zero warnings: strInstructions in five languages intact, strDrinkThumb photo URLs untouched, and idDrink still the string "11007" so tutorial code doing strict comparisons keeps working. Your ingredient loop over strIngredient1βstrIngredient15 runs unchanged.
| TheCocktailDB | Your Mockbird project |
|---|---|
search.php?s=margarita | /drinks?strDrink_like=margarita β same case-insensitive substring semantics |
lookup.php?i=11007 | /drinks/1 β or filter ?idDrink=11007; a bad id is a real 404, not 200 null |
filter.php?c=Ordinary_Drink | /drinks?strCategory=Ordinary%20Drink |
filter.php?g=Cocktail_glass | /drinks?strGlass=Cocktail%20glass |
filter.php?a=Alcoholic | /drinks?strAlcoholic=Alcoholic |
filter.php?i=vodka (one drink!) | ?q=vodka β matches the ingredient in any strIngredient slot and returns every hit |
| filter's slim 3-field shape | ?select=strDrink,strDrinkThumb,idDrink |
random.php | ?sortBy=random&limit=1 β a different draw every request |
| 100-item listing cap | ?page=2&limit=10 + X-Total-Count header |
| multi-ingredient filter (Premium) | honest gap: q= is one term; chain exact matches like ?strIngredient1=Tequila only if you know the slot |
curl "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/drinks?strDrink_like=blue&select=strDrink"
# β [{"id":2,"strDrink":"Blue Margarita"}]
curl "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/drinks?q=curacao&select=strDrink"
# β finds the curaΓ§ao drink by INGREDIENT β every slot searched, every hit returned
Tutorial code reads data.drinks. One query param reproduces that envelope exactly:
curl "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/drinks?mock_envelope=%7B%22drinks%22%3A%22%24data%22%7D&strDrink_like=tommy&select=strDrink"
# β {"drinks":[{"id":3,"strDrink":"Tommy's Margarita"}]}
# no match? you get {"drinks":[]} β an ARRAY, every time.
# not null, not the string "no data found". One shape, one code path.
Set it once as the project default (PUT /api/projects/:id/settings {"envelope":β¦}) and every GET answers in TheCocktailDB'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/drinks \
-H 'content-type: application/json' \
-d '{"strDrink":"Test Paloma","strCategory":"Ordinary Drink","strIngredient1":"Tequila"}'
# β 201 {"id":7,β¦} β and GET /drinks/7 returns it. It persists.
That turns the standard read-only cocktail grid into full CRUD: an add-a-recipe form, an edit page, favourites that survive a refresh. 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/drinks?mock_seq=429,429,200"
# slow response for your skeleton/loading state
curl "https://mockbird.mockbird.workers.dev/m/PROJECT_ID/drinks?strDrink_like=marg&mock_delay=2000"
More recipes in testing loading & error states.
Credit where due: TheCocktailDB's content is the product β hundreds of real, crowd-sourced cocktail recipes with photos, categories, glass types, and multilingual instructions, maintained for years and answering our checks in under half 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 one-off Premium fee is a fair deal if you ship a real drinks app on their data β it buys the full ingredient filter, multi-ingredient search, and an app-store licence. The honest split: real drink content and photos β TheCocktailDB. Your own records with full search, writes, one consistent response shape, 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=zzzznotadrink and lookup.php?i=99999 both answer 200 {"drinks":null} while filter.php?c=NotACategory and filter.php?g=NotAGlass answer 200 {"drinks":"no data found"} (a string); filter.php?i=vodka|gin|rum|tequila each returned exactly 1 drink on the test key, as did popular.php, latest.php, and randomselection.php (all marked "*Premium API only" on api.php); filter.php?c=Cocktail returned exactly 100 items and the docs state Premium subscribers "can also list the full database rather than limited to 100 items"; the docs state the test key 1 is for development or educational use and that a Premium key is required "if releasing publicly on an appstore", with multi-ingredient filtering Premium-only. Every Mockbird command on this page was run against a live project before publishing.
Full API reference in the docs. More guides: TheMealDB alternative Β· FakeStoreAPI alternative Β· DummyJSON alternative Β· Mock API for React Β· Host a JSON file as an API. Create your API β