Honest split first: Mock Service Worker is a genuinely great library. If you're writing JavaScript unit or component tests and you want request interception inside the test process โ per-test handler overrides, no network, mocks that target the real API URL without changing your app's base URL โ MSW is the right tool and this page is not trying to talk you out of it.
But MSW has a hard boundary that sends people searching for alternatives: the mock only exists inside your JavaScript process. A service worker in your browser tab, or interceptors in your Node test runner โ and nowhere else. Which means:
npx msw init), setupWorker/setupServer wiring, conditional startup โ heavy for "I just need data on a URL for a prototype." (And if you learned MSW from a pre-v2 tutorial, the rest.get(url, (req, res, ctx) => โฆ) snippets don't work anymore โ the v2 API changed the handler signature.)Mockbird is the other half: a hosted mock REST API on a real URL. Define resources (or pick a preset, or import an OpenAPI spec / db.json / CSV / Postman collection), get realistic seeded data and full stateful CRUD โ reachable from a browser, curl, Postman, a phone, a backend, CI, or a teammate on another continent. No handler code, no service worker, no signup.
# one curl, no signup โ a whole seeded e-commerce backend
curl -X POST https://HOST/api/projects \
-H 'content-type: application/json' -d '{"name":"shop","preset":"ecommerce"}'
# โ {"id":"abc123","adminKey":"KEY", ...} โ save both
curl https://HOST/m/abc123/products?limit=3
# โ 3 seeded products, CORS on, writes persist
Prefer clicking? This link creates the same project in your browser โ no account. Have an OpenAPI spec? Paste it at /app#import and get a live seeded mock of your actual API shape.
| MSW | Mockbird | Notes |
|---|---|---|
http.get('/products', โฆ) handler | a products resource | list/get/create/update/delete generated, plus filtering, sorting, pagination, relations |
| Hand-rolled response data | seeded realistic records | faker-style names/emails/prices/dates; or import your exact records from db.json/CSV |
HttpResponse.json(data, {status: 500}) | ?mock_status=500 on any URL | no handler edit, no rebuild โ error/loading states guide |
delay(2000) | ?mock_delay=2000 | plus ?mock_jitter=100-1500 random latency and ?mock_chaos=0.3 random failures |
| Stateful handlers you write yourself | stateful by default | POST persists; GET it back over REST or GraphQL |
Per-test server.use(โฆ) overrides | snapshot pinning | save named data states, serve any GET from one via X-Mockbird-Snapshot โ parallel workers don't race |
onUnhandledRequest logging | request inspector | last 50 requests: method, path, query, headers, body |
graphql.query(โฆ) handlers | generated GraphQL endpoint | typed schema from your resources, GraphiQL included |
| Handlers shared via your repo | shared via URL | send a teammate the base URL โ nothing to install |
# seeded data with field projection
curl 'https://HOST/m/demo/products?limit=2&select=name,price'
# the states you'd write handlers for โ as query params
curl -i 'https://HOST/m/demo/products?mock_status=503'
curl 'https://HOST/m/demo/products/1?mock_delay=2000'
# stateful: the write persists
curl -X POST https://HOST/m/demo/products \
-H 'content-type: application/json' -d '{"name":"proof","price":9.99}'
# โ returns {"id":31,โฆ} โ and GET /m/demo/products/31 now works
All against the shared demo project (resets daily).
Plenty of teams keep MSW for unit tests and use a hosted mock everywhere MSW can't go. Two easy patterns:
API_BASE_URL to your project URL in dev/preview builds; your app code doesn't change, and now the mock also works for the mobile app, the designer's phone, and CI smoke tests against a deployed preview.| MSW | Mockbird | |
|---|---|---|
| What it is | free OSS library (JS/TS) | free hosted service |
| Reachable from | your JS process only | anything with HTTP: browser, curl, Postman, mobile, backend, CI, agents |
| Setup | write handlers + worker init + wiring | one curl or one click; no code |
| Mock data | you write it | seeded realistic data, or import your own |
| Stateful CRUD | DIY in handler code | default |
| Error/latency simulation | per-handler code | ?mock_status / ?mock_delay / ?mock_chaos / ?mock_jitter on any URL |
| Works offline | yes โ no network at all | no โ it's a real network call |
| Latency | zero (in-process) | real network latency |
| Mocks the real URL transparently | yes โ intercepts requests to your actual API host | no โ you point your app at a Mockbird base URL |
| Per-test isolation | excellent (server.use, reset between tests) | snapshot pinning + restore recipes |
| Dynamic programmable responses | full JS โ anything you can code | custom routes with templating; less powerful than arbitrary code |
| Request cap | none | 10,000/project/day |
Written by the Mockbird maker โ bias disclosed. Where MSW genuinely wins: in-process interception means zero network flakiness, zero latency, and tests that run offline; it mocks your real API URLs without touching app config; per-test handler overrides are cleaner than anything URL-based can be; and a handler is arbitrary JavaScript, so any conditional behavior you can imagine is expressible. For JS unit and component tests, keep MSW. When the thing you need is a URL โ for a prototype, a mobile client, a backend consumer, a teammate, or CI hitting a deployed build โ that's us.
Full API reference in the docs. More guides: Mirage JS alternative ยท mock API for React ยท free mock API tools compared ยท deterministic test data ยท testing loading & error states. Create your API โ