โ† All guides

A json-graphql-server alternative: same db.json, hosted, with a REST twin

Credit first: json-graphql-server is one of the nicest ideas in the mock-API space โ€” hand it a single file of JSON and it derives a full GraphQL schema: typed fields, all<Type> lists with pagination and sorting, per-field filter arguments (views_gt, views_lte, _neqโ€ฆ), relationship traversal from user_id-style foreign keys, working CRUD mutations, and GraphiQL in the browser. It comes from marmelab (the react-admin folks) and โ€” unlike much of this category โ€” it is actively maintained: v3.3.1 shipped in June 2026, the repo saw commits this month, ~1,950 stars, MIT, ~3,250 downloads/week. If your whole need is a GraphQL fixture inside a Node test process or a webpack dev server, keep it. It's good.

The walls only appear when the mock needs to outlive a process or be reached by anything that isn't your laptop. We ran v3.3.1 ourselves in August 2026; everything below is from that transcript.

The four walls (verified on v3.3.1)

1. Writes evaporate on restart. Mutations genuinely work โ€” createPost returned {"id":"3"} and subsequent queries saw it โ€” but the store is process memory. We restarted the server and post 3 was gone. Fine for unit tests; a wall the moment the mock is a shared dev sandbox, a demo backend, or anything CI spins up in one job and asserts on in another.

2. It binds to localhost. The entire CLI surface is -p/--port and -h/--host โ€” we read the 30-line bin script. Default host is literally localhost; a teammate, a phone on the same Wi-Fi, a deployed preview build, or a CI browser job gets nothing unless you rebind and self-host the process somewhere.

3. GraphQL-only โ€” and the failure mode is sneaky. There is no REST view of the data. Worse: every path serves the GraphiQL HTML page with HTTP 200 โ€” we curled GET /posts and got back the GraphiQL app shell. A component that does fetch('/posts').then(r => r.json()) doesn't get a 404 it can handle; it gets 200 OK and then throws on parse.

4. No failure simulation. No flag for latency, no error injection, no rate-limit rehearsal. The happy path is the only path. (Also a small assertion gotcha: ids are typed GraphQLID, so the number 1 in your file comes back as the string "1" in every response.)

One fix-first tip while you're here: the CLI require()s your data file, so a db.js written as an ES module (export default {โ€ฆ}) crashes with the baffling TypeError: e.reduce is not a function. Use module.exports = {โ€ฆ} (or a .cjs extension) and it starts fine.

Take the same db.json here instead

Mockbird accepts the exact same file shape โ€” top-level keys become collections, records are kept verbatim โ€” and gives back a hosted project that speaks both GraphQL and REST over the same store, with writes in a real database:

BASE=https://mockbird.mockbird.workers.dev
curl -s -X POST $BASE/api/projects/import -H 'content-type: application/json' -d '{
  "name": "jgs-migrate",
  "db": {
    "posts": [
      { "id": 1, "title": "Lorem Ipsum",    "views": 254, "user_id": 123 },
      { "id": 2, "title": "Sic Dolor amet", "views": 65,  "user_id": 456 }
    ],
    "users": [
      { "id": 123, "name": "John Doe" },
      { "id": 456, "name": "Jane Doe" }
    ]
  }}'
# โ†’ {"id":"<PID>","adminKey":"โ€ฆ","baseUrl":"https://mockbird.mockbird.workers.dev/m/<PID>", โ€ฆ}
โšก Skip the terminal: paste your db.json at /app#import โ€” it becomes a live hosted API in seconds, no signup. Or create a seeded blog backend in one click.

That's the dataset from json-graphql-server's own README. Now both protocols work โ€” note the user_id foreign key joins in each of them, snake_case and all, and ids stay integers:

# GraphQL (GraphiQL lives at this URL in a browser):
curl -s $BASE/m/<PID>/graphql -H 'content-type: application/json' \
  -d '{"query":"{ post(id:1) { title views user { name } } }"}'
# โ†’ {"data":{"post":{"title":"Lorem Ipsum","views":254,"user":{"name":"John Doe"}}}}

# REST twin of the same records:
curl -s '$BASE/m/<PID>/posts?views_gte=100'          # range filters
curl -s '$BASE/m/<PID>/posts/1?_expand=user'         # same join, REST-style

Mutations write to D1 (SQLite at the edge), so they're still there after every restart we don't have โ€” and the REST side sees them instantly:

curl -s $BASE/m/<PID>/graphql -H 'content-type: application/json' \
  -d '{"query":"mutation{ createPost(input:{title:\"New one\",views:1,user_id:123}) { id } }"}'
# โ†’ {"data":{"createPost":{"id":3}}}
curl -s $BASE/m/<PID>/posts/3
# โ†’ {"id":3,"title":"New one","views":1,"user_id":123}   โ† same write, other protocol

And the failure drills json-graphql-server has no flags for:

# 2s of latency on the GraphQL endpoint (we measured 2.13s):
curl '$BASE/m/<PID>/graphql?mock_delay=2000' -X POST -H 'content-type: application/json' \
  -d '{"query":"{ postsCount }"}'

# chaos injection โ€” this fraction of requests fails with a 5xx + errors[] payload:
curl '$BASE/m/<PID>/graphql?mock_chaos=0.3' -X POST -H 'content-type: application/json' \
  -d '{"query":"{ postsCount }"}'

# deterministic error sequences on the REST twin (exactly two 500s, then success):
curl '$BASE/m/<PID>/posts?mock_seq=500,500,200&mock_seq_key=ci-run-1'   # โ†’ 500 500 200

Translation table

json-graphql-serverMockbird GraphQLMockbird REST twin
allPostspostsGET /posts
Post(id: 1)post(id: 1)GET /posts/1
_allPostsMeta { count }postsCountX-Total-Count header
page / perPagepage / limit?page=&limit= (also _page/_limit)
sortField / sortOrdersortBy / order?sortBy=&order=
filter: { views_gt: 100 }where (exact-match, typed)?views_gt=100 (+ _gte/_lte/_lt/_ne/_like)
filter: { q: "dolor" }q argument?q=dolor
Post { User { name } } via user_idpost { user { name } } โ€” same FK convention?_expand=user / ?_embed=posts
createPost(title: โ€ฆ) (flat args)createPost(input: {โ€ฆ})POST /posts
updatePost / deletePostupdatePost / deletePostPATCH / DELETE /posts/:id
GraphiQL at /GraphiQL at /m/<PID>/graphqlโ€”

One honest asymmetry in that table: json-graphql-server generates range filter arguments (_lt/_lte/_gt/_gte/_neq) inside GraphQL itself, which is genuinely nice โ€” our GraphQL where is typed exact-match, and the range operators live on the REST twin instead. If your queries lean hard on GraphQL-side range filters, that's a real difference, not a spin.

What you get that has no equivalent there

NeedMockbirdNotes
URL a teammate/phone/CI can hithosted, CORS onno tunnel, no self-hosting, no signup
Writes that surviveD1-backed persistenceyour QA's bug-repro data is still there tomorrow
Known data states per testsnapshots + per-request pinningparallel workers each pin their own scenario
Failure rehearsalmock_delay / mock_chaos / mock_jitter (GraphQL too), mock_status / mock_seq / mock_ratelimit (REST)test retry logic against deterministic sequences
See what your app actually sentrequest inspectorlast 50 requests, headers + bodies
Contract artifactsopenapi.json, types.ts, ?format=zod, Postman collectiongenerated from the same schema
Leave anytime/db.json ejectround-trips straight back into json-graphql-server

Try it in 10 seconds (shared demo, no setup)

# GraphiQL in your browser:
#   https://mockbird.mockbird.workers.dev/m/demo/graphql

curl https://mockbird.mockbird.workers.dev/m/demo/graphql \
  -H 'content-type: application/json' \
  -d '{"query":"{ products(sortBy: \"price\", order: \"desc\", limit: 2) { id name price } }"}'

# same data over REST โ€” one store, two protocols:
curl 'https://mockbird.mockbird.workers.dev/m/demo/products?sortBy=price&order=desc&limit=2'

Honest comparison

json-graphql-serverMockbird
Pricefree, open source (MIT)free while in beta
Inputdb.json / db.jssame db.json โ€” plus OpenAPI, CSV, Postman, HAR
ProtocolsGraphQL only (other paths return GraphiQL HTML, HTTP 200)GraphQL + REST over one store
Writesin-memory, lost on restartpersistent (D1/SQLite)
Reachable fromlocalhost (rebind + self-host to share)any device, hosted URL
GraphQL range filtersyes โ€” generated _lt/_gt/โ€ฆ argsexact-match where; range ops on REST
Failure simulationnonedelay, jitter, chaos, status, sequences, rate-limit
Record idsstringified ("1")stay integers
In-browser client mock (XHR)yes โ€” uniqueno
Works offlineyesno
Request capsnone10k/project/day
Maintenanceactive (v3.3.1 Jun 2026, marmelab)actively developed

Written by the Mockbird maker โ€” bias disclosed. Where json-graphql-server genuinely wins: it runs offline with zero latency and no request caps; its generated GraphQL-side range-filter arguments are richer than our where; and its browser/XHR-mock build โ€” mocking a GraphQL endpoint inside the page for client unit tests โ€” has no Mockbird equivalent at all. It is also actively maintained by a serious shop; nothing here is an abandonment story. Every claim above was verified by us in August 2026 on v3.3.1: the restart-wipe, the localhost bind and 30-line CLI, the GraphiQL-on-every-path behavior, the stringified ids, and the full Mockbird transcript (snake_case user_id joins in both protocols, mutation โ†’ REST read-back, measured 2.13s delay) ran against the live service before publishing. If any of this changes, we'll update the page.

Full GraphQL reference in the docs. More guides: free GraphQL mock API ยท @graphql-tools/mock alternative ยท graphql-faker alternative ยท json-server, hosted ยท host a JSON file as an API ยท deterministic test data. Create your API โ†’