Same resources, same field names, same nested routes. The migration is one string in your code. What you get for it is the thing JSONPlaceholder cannot do: ask for the failure you are trying to test against.
- fetch('https://jsonplaceholder.typicode.com/posts') + fetch('https://flakyapi.dev/v1/posts')
Two differences in that line: the host, and a /v1 prefix. The
prefix exists so the API can change later without breaking the URLs you
have already published — the exact problem JSONPlaceholder has, having
never versioned.
A find-and-replace across a project is usually enough:
sed -i '' 's|jsonplaceholder.typicode.com/|flakyapi.dev/v1/|g' src/**/*.js
Every shared resource returns the same field names, so nothing downstream of the fetch has to change — not your types, not your components, not your tests.
| Resource | Fields | JSONPlaceholder | flaky |
|---|---|---|---|
| posts | id, userId, title, body | 100 | 100 |
| comments | id, postId, name, email, body | 500 | 500 |
| albums | id, userId, title | 100 | 100 |
| photos | id, albumId, title, url, thumbnailUrl | 5,000 | 1,000 |
| todos | id, userId, title, completed | 200 | 200 |
| users | id, name, username, email, address, phone, website, company | 10 | 10 |
| products | id, title, price, rating, stock, brand, category… | — | 100 |
Nested routes work the same way — /v1/posts/1/comments,
/v1/albums/1/photos, /v1/users/1/todos — and so
does filtering by any field, /v1/todos?userId=3&completed=true.
Paging accepts both styles. _start and _limit, the
json-server form JSONPlaceholder uses, work unchanged; _page and
_limit also work if you prefer pages. So do _sort
and _order.
The dataset ships inside the Worker so a list request is an array slice rather than a database read. Five thousand photo rows would be two thirds of the bundle for a resource almost nobody paginates past the first page of. If you genuinely need 5,000, say so and it can go up.
1,000 requests a day anonymously; 10,000 with a free key that takes one
request to create and needs no confirmation email. JSONPlaceholder
publishes no limit. Every response carries
x-ratelimit-remaining so you are never guessing.
POST, PUT, PATCH and
DELETE are echoed and not stored, exactly as on
JSONPlaceholder. The difference is the response carries
x-mock-write: not-persisted, so nobody loses an afternoon
discovering it. When you need writes that stick, take a sandbox.
_param is an error
JSONPlaceholder treats ?_typo=1 as a filter on a field
named _typo, matches nothing, and returns [].
Here it returns a 400 naming the parameters that exist. An
empty array that means "you made a typo" costs more time than an error.
Every fake API hands you data. None of them let you ask for the thing you are
actually testing: the slow response, the 503, the endpoint that fails one time
in three. That is normally a local mock server, a branch in your code, or a
setTimeout you have to remember to delete.
# your loading state, with a real slow response GET /v1/posts?_delay=3000 # your error boundary, with a real 503 GET /v1/posts?_status=503 # your retry logic, failing one request in three GET /v1/posts?_fail_rate=0.3 # slow and unreliable at once, because that is what production is GET /v1/products?_delay=1500&_fail_rate=0.2
It works on every endpoint, including nested routes. No install, no config, no branch in your code that has to be removed before you ship — and it works from a browser console, a CodePen, or a colleague's machine with nothing set up.
Out-of-range values are rejected rather than ignored:
?_status=999 returns a 400, not a silent
200. A tool for testing failures should not fail quietly itself.
Paste either of these into a terminal — nothing to sign up for.
curl 'https://flakyapi.dev/v1/posts?_limit=2' curl -i 'https://flakyapi.dev/v1/posts?_status=503'
Or use the live inspector on the home page, which lets you set the delay, status and failure rate and watch the response change.