Mock a movie API โ€” build your movie app without a TMDB key

The movie app is probably the single most-built portfolio project there is โ€” every React, Vue and Flutter course has one โ€” and step one is always the same: go sign up for a TMDB API key. Keyless requests get you nowhere on either of the two APIs the tutorials use:

curl https://api.themoviedb.org/3/movie/550
# โ†’ 401 {"status_code":7,"status_message":"Invalid API key: You must be granted a valid key."}

curl "https://www.omdbapi.com/?t=inception"
# โ†’ 401 {"Response":"False","Error":"No API key provided."}

Signup is a small tax once โ€” but it compounds: a classroom of thirty students is thirty signups; a frontend-only deploy means your key ships in the JavaScript bundle where anyone can lift it; CI needs the key as a secret; and neither API will serve you a 500 or a slow response on demand when you want to build decent loading and error states. While you're building the UI, none of that buys you anything.

To be fair: TMDB is one of the best free APIs on the internet โ€” a huge community-maintained catalogue, real posters, genuinely free for non-commercial use (with attribution), and the key signup takes minutes. OMDb's free key is 1,000 requests/day. If your app should show real movies, use them โ€” ideally with the key kept server-side. A mock is for the other cases: building and deploying the frontend before you care about data, workshops and classrooms, CI and tests that assert on known records, drilling error paths, and write features (watchlists, reviews) that the real APIs gate behind user auth.

1. A movie API in 30 seconds (one paste, no signup)

BASE=https://mockbird.mockbird.workers.dev
P=$(curl -s -X POST $BASE/api/projects -H 'content-type: application/json' \
     -d '{"name":"movie-mock","blank":true}')
PID=$(echo $P | sed 's/.*"id": *"\([^"]*\)".*/\1/')
KEY=$(echo $P | sed 's/.*"adminKey": *"\([^"]*\)".*/\1/')

curl -s -X POST $BASE/api/projects/$PID/resources -H "x-admin-key: $KEY" \
  -H 'content-type: application/json' -d '{
  "name":"movies","seed":25,
  "fields":[{"name":"title","type":"title"},
            {"name":"year","type":"oneOf","values":[1972,1994,1999,2008,2014,2019,2023]},
            {"name":"genre","type":"oneOf","values":["action","comedy","drama","sci-fi","horror","romance"]},
            {"name":"rating","type":"rating"},
            {"name":"poster","type":"image"},
            {"name":"overview","type":"paragraph"},
            {"name":"director","type":"fullName"}]
}'

curl "$BASE/m/$PID/movies?_limit=1"

That returns 25 seeded movies shaped like this โ€” note the poster is a URL that actually renders (more below):

{
  "id": 1,
  "title": "Eye Moment Power Cloud Hour",
  "year": 1994,
  "genre": "comedy",
  "rating": 1.5,
  "poster": "https://mockbird.mockbird.workers.dev/m/YOUR_ID/img/640x480?seed=1511",
  "overview": "Music month road problem case child window. โ€ฆ",
  "director": "Olivia Martinez"
}

oneOf picks verbatim from the values you list (the years above come back as real integers), rating is a 1โ€“5 float, and every field type is swappable โ€” see field types.

2. Query it like a movie API

Everything a movie-browser UI needs is a query parameter โ€” no extra endpoints to define:

# search box  (TMDB: /search/movie?query=โ€ฆ)
curl "$BASE/m/$PID/movies?q=cloud"

# genre tab   (TMDB: /discover/movie?with_genres=โ€ฆ)
curl "$BASE/m/$PID/movies?genre=sci-fi"

# "Top rated" (TMDB: sort_by=vote_average.desc)
curl "$BASE/m/$PID/movies?sortBy=rating&order=desc&_limit=5"

# minimum-rating slider (TMDB: vote_average.gte)
curl "$BASE/m/$PID/movies?rating_gte=4"

# pagination for the grid โ€” X-Total-Count header included
curl -i "$BASE/m/$PID/movies?_page=2&_limit=10"

# detail page
curl "$BASE/m/$PID/movies/1"

# lighter list payloads for the grid
curl "$BASE/m/$PID/movies?select=title,rating,poster"

3. Posters that render

The image field type points at Mockbird's own placeholder-image endpoint โ€” deterministic SVG posters served from the same host, so <img src={movie.poster}> just works with no third-party image service, no hotlink breakage, and no key. You can also compose poster URLs yourself, at poster-ish aspect ratios, with a label:

https://mockbird.mockbird.workers.dev/m/YOUR_ID/img/300x450?seed=7&text=Poster

Same seed โ†’ same colors forever, which keeps visual-regression screenshots stable. Details in the placeholder image guide.

4. A watchlist that persists (the part real movie APIs gate)

On TMDB, watchlist writes require user authentication. Here, writes are the point โ€” add a second resource and POST to it:

curl -s -X POST $BASE/api/projects/$PID/resources -H "x-admin-key: $KEY" \
  -H 'content-type: application/json' \
  -d '{"name":"watchlist","seed":0,"fields":[{"name":"movieId","type":"refId"},{"name":"note","type":"sentence"}]}'

# "Add to watchlist" button
curl -X POST "$BASE/m/$PID/watchlist" -H 'content-type: application/json' \
  -d '{"movieId":1,"note":"must see"}'

# watchlist page โ€” join the full movie record onto each entry
curl "$BASE/m/$PID/watchlist?_expand=movie"

The write persists: it shows up in every later GET, survives page reloads, and the _expand join gives your watchlist page full movie objects in one request. POSTing new movies works the same way โ€” a "suggest a film" form is one fetch.

5. Drill the states the real API won't serve you

# skeleton screens โ€” a real 2-second response
curl "$BASE/m/$PID/movies?mock_delay=2000"

# error page โ€” a real 500
curl -i "$BASE/m/$PID/movies?mock_status=500"

# retry logic โ€” fails twice, then succeeds, deterministically
curl -i "$BASE/m/$PID/movies?mock_seq=500,500,200"

Recipes for every state in testing loading & error states.

Honest comparison

TMDB (free)OMDb (free)Mockbird
Real movie data & postersโœ” โ€” excellentโœ”โœ— โ€” seeded fake data you define
API key / signupkey required (non-commercial use, attribution)key by email, 1,000 req/daynone
Key safe in a frontend-only deployโœ— โ€” ships in your bundleโœ—no key exists
Writes (watchlist, reviews)user auth requiredโœ— read-onlyโœ” persist, no auth
Deterministic data for testsโœ— โ€” live catalogueโœ—โœ” (?seed=, snapshots)
Trigger 500/slow/429 on demandโœ—โœ—โœ” query params
Requestsgenerous1,000/day10,000/project/day

Rule of thumb: shipping a real movie product โ†’ TMDB with the key kept server-side. Building the UI, teaching a class, running tests, or keeping a portfolio demo alive โ†’ mock it, then swap the base URL when you're ready (and label the demo data as sample content).

Notes & limits

Create yours

The one-paste block in section 1 is the whole setup โ€” or start in the dashboard and click instead. The same project can also mock a news API, a weather API, any third-party API your app calls, serve a full CRUD REST API, and hand your test suite deterministic fixtures.