The Hacker News API is great โ€” and it takes 31 requests to render the front page

Credit first, because it's due: the official Hacker News API is everything a free public API should be. No key, no signup, real CORS headers (access-control-allow-origin: * โ€” browser fetch() just works), hosted on Firebase, and in years of "build an HN reader" tutorials nobody's ever had to write the "is it down?" paragraph. That's why it's the default first-API exercise for React, Vue, and Swift alike.

Then you hit its shape. It's a Firebase realtime database export, and Firebase thinks in single keys โ€” so the entire query surface is "give me one thing by id" (verified September 2026):

curl https://hacker-news.firebaseio.com/v0/topstories.json
# โ†’ [49763697, 49763928, 49763987, โ€ฆ]   500 ids. Just ids.

curl https://hacker-news.firebaseio.com/v0/item/49763697.json
# โ†’ the actual story โ€” one request per id

curl "https://hacker-news.firebaseio.com/v0/item/49763697,49763928.json"
# โ†’ null                                โ† no batch endpoint

curl "https://hacker-news.firebaseio.com/v0/item.json?orderBy=%22score%22"
# โ†’ {"error": "Permission denied"}      โ† no queries on items, ever

So a 30-story front page is 31 HTTP requests โ€” the id list, then N+1 waterfall through every story. There's no search, no "stories over 200 points", no field selection, and no sorting beyond the order of the id array. Every HN-reader tutorial quietly becomes a lesson in Promise.all before it gets to render anything.

1. The quick fix for reads: Algolia's HN Search API

Also genuinely good, also free and keyless and CORS-open: hn.algolia.com/api/v1 indexes all of HN and returns full story objects, thirty at a time, in one request (verified September 2026):

curl "https://hn.algolia.com/api/v1/search?tags=front_page"
# โ†’ the whole front page, one request, with title/points/author/num_comments

curl "https://hn.algolia.com/api/v1/search?tags=story&numericFilters=points>500"
# โ†’ range filters work too

Where you'll hit its edges: results come in exactly two orderings โ€” relevance (/search) or date (/search_by_date) โ€” so "front page sorted by points" is client-side sorting again; every hit carries a _highlightResult block several times the size of the story itself; the shapes differ from the Firebase items (string objectID, different field names); and it's read-only, which is fine until your exercise adds an upvote button.

2. Own a snapshot: the front page in one import

The top 30 stories are ~11KB of JSON. Capture them once and host your own copy โ€” no signup:

curl -s https://hacker-news.firebaseio.com/v0/topstories.json |
  jq -r '.[:30][]' | while read id; do
    curl -s "https://hacker-news.firebaseio.com/v0/item/$id.json"
  done | jq -s '{stories: map(del(.kids))}' > db.json

curl -s -X POST "https://mockbird.mockbird.workers.dev/api/projects/import?name=hn" \
  -H 'content-type: application/json' --data-binary @db.json
# โ†’ {"id":"YOUR_ID","adminKey":"YOUR_ADMIN_KEY",
#    "resources":[{"name":"stories","records":30,โ€ฆ}]}

(We drop kids โ€” comment ids you didn't capture โ€” but keep everything else.) Because HN ids are plain integers, the importer preserves them verbatim: /stories/49763697 resolves with the real HN id, so deep links and item URLs built from your data still make sense.

3. One request instead of 31 โ€” and queries Firebase will never allow

All of these ran against a live import while writing this guide:

BASE=https://mockbird.mockbird.workers.dev/m/YOUR_ID

curl "$BASE/stories?sortBy=score&order=desc&limit=3&select=title,score"
#   โ†’ the actual top-by-points list neither upstream can give you sorted

curl "$BASE/stories?score_gte=200"        # range filter โ€” X-Total-Count: 8
curl "$BASE/stories?q=ai"                 # full-text across every field
curl "$BASE/stories?title_like=cloudflare"  # substring, case-insensitive
curl "$BASE/stories?by=emigre"            # exact author match

And writes are real, which is what an HN-client exercise actually needs:

curl -X PATCH "$BASE/stories/49763697" \
  -H 'content-type: application/json' -d '{"voted":true}'
# โ†’ your upvote button has a backend; GET-back shows voted:true

curl -X POST "$BASE/stories" -H 'content-type: application/json' \
  -d '{"title":"Show HN: my thing","by":"me","score":1,"type":"story"}'
# โ†’ persists โ€” X-Total-Count is now 31

/m/YOUR_ID/types.ts generates a TypeScript Story interface from your records (?format=zod for Zod), and failure drills are a query param away: ?mock_delay=3000, ?mock_status=503, ?mock_ratelimit=5.

One more thing a snapshot buys you: the real front page won't hold still. While writing this guide, two captures ten minutes apart contained the same 30 stories in a different order. A live-HN test suite or a screenshot test drifts by the minute; a snapshot is the same 30 stories, in the same order, every run โ€” and named snapshots let you keep an "empty", an "ask-hn-heavy", and a "300-comment-thread" scenario side by side.

4. Honest comparison

Firebase (official)Algolia HN SearchYour snapshot
Live dataโœ” the real thingโœ” indexed, searchableโœ˜ frozen at capture (that's the point)
Front page31 requestsโœ” 1 requestโœ” 1 request
Searchโœ˜ noneโœ” excellent โ€” its whole jobโœ” ?q=, ?title_like=
Sort by pointsโœ˜โœ˜ relevance or date onlyโœ” ?sortBy=score&order=desc
Range filtersโœ˜โœ” numericFiltersโœ” ?score_gte=
Field selectionโœ˜โœ˜ (+_highlightResult bloat)โœ” ?select=title,score
Writes persistโœ˜ read-onlyโœ˜ read-onlyโœ” POST/PATCH/DELETE are real
Deterministic for testsโœ˜ reshuffles constantlyโœ˜โœ” same data every run

The fair split: a real HN reader that people use should read the real API โ€” Firebase for item detail, Algolia for lists and search, and both are excellent at what they chose to be. The moment you're building or testing that reader โ€” sorting by points, wiring an upvote button, asserting on a fixed page of stories โ€” spend the one curl and own the copy. Related: Rick and Morty API, PokรฉAPI, deterministic test data, mock any third-party API.

โšก Skip the terminal: paste your db.json in the dashboard importer (CSV, OpenAPI, Postman, and HAR work too) โ€” or create a seeded sandbox in one click. No signup.