mock-server

Turn an OpenAPI 3.1 spec into editable, relationally-consistent mock data — served over HTTP as a drop-in mock, or dumped as static fixtures.
Most mock tools generate each endpoint in isolation, so GET /orders returns a userId that doesn't exist in GET /users. mock-server infers the relations between your schemas and materializes one consistent dataset: every foreign key points at a real row, and reads expand those ids into the nested objects your schema declares.
OpenAPI 3.1 spec ─▶ entity model + inferred relations ─▶ consistent seed data ─▶ HTTP (two planes)
Written in Go: a single static binary, no runtime dependencies.
Quick start
# build the binary (Go ≥ 1.26)
go build -o mock ./cmd/mock
# …or install it onto your PATH
go install github.com/thiszona/mock-plane/cmd/mock@latest
# run the bundled demo (Shop API: users → orders → items → products)
./mock demo
# mock-plane: http://127.0.0.1:<port>
# control-plane: http://127.0.0.1:<port>/__mock
# inspect the relation graph inferred from a spec
./mock graph ./openapi.yaml
# dump deterministic fixtures as JSON
./mock dump ./openapi.yaml --count 10 --out fixtures.json
# run a live mock of your own spec
./mock serve ./openapi.yaml --port 8080
No Go toolchain? Use the Docker image.
Two planes
A single server exposes two clearly separated surfaces:
Mock-plane — /<entity> · /<entity>/<id>
A spec-faithful drop-in: raw bodies that match your OpenAPI schemas, with no wrapper. Foreign-key ids are expanded into nested objects on read, exactly as the schema declares them.
curl http://127.0.0.1:8080/orders
# [ { "id": "...", "userId": "d4a6…", ← scalar FK stays a string (schema says string)
# "status": "paid", "total": 42.5,
# "items": [ { "id": "...", "quantity": 2,
# "product": { "id": "...", "name": "…",
# "category": { "id": "...", "name": "…" } } } ] } ]
| Method |
Path |
Result |
GET |
/<entity> |
bare array, FK-expanded · ?limit=&offset= slice · X-Total-Count header |
GET |
/<entity>/<id> |
bare object, FK-expanded · 404 if missing |
POST |
/<entity> |
create · 201 + Location · 409 on duplicate id · 422 on dangling FK |
PUT / PATCH |
/<entity>/<id> |
update (shallow merge) · 404 if missing |
DELETE |
/<entity>/<id> |
204 · 404 if missing |
Routes are entity-driven: every schema gets full CRUD even if the spec declares few paths. Path stems are matched tolerantly (/users, /addresses, /categories all resolve).
Control-plane — /__mock/*
The tool's own API for authoring and introspection, wrapped in a consistent envelope { success, data, error, meta }.
| Method |
Path |
Purpose |
GET |
/__mock/health |
liveness + entity count |
GET |
/__mock/model |
inferred entities + relations (incl. abstain edges) |
GET |
/__mock/routes |
synthesized route table |
GET |
/__mock/collections |
entity names + row counts |
GET/POST |
/__mock/collections/<entity> |
list (paginated, raw rows) / create |
GET/PATCH/DELETE |
/__mock/collections/<entity>/<id> |
fetch / update / delete a raw row |
POST |
/__mock/reset |
restore the original materialized seed |
GET/POST/DELETE |
/__mock/injection |
read / set / clear fault injection |
POST |
/__mock/scenario/save·/load |
snapshot the store / hydrate a snapshot |
The control-plane shows raw rows (foreign-key ids, not expanded) — the edit-plane view for authoring.
Multi-project
One server can host many specs at once. Pass two or more specs to serve and each becomes a project mounted at its id at the root — /<id>/… — behind a single port:
mock serve examples/demo-spec.yaml examples/tracker-spec.yaml --port 8080
# http://127.0.0.1:8080/demo-spec/... (Shop API)
# http://127.0.0.1:8080/tracker-spec/... (Tracker API)
# projects-api: http://127.0.0.1:8080/__mock/projects
Each project keeps its own two planes — /<id>/<entity> (mock) and /<id>/__mock/* (control). The only reserved root is the control prefix /__mock; project ids are [a-z0-9-], so they can never collide with it. A registry API manages projects at runtime:
| Method |
Path |
Purpose |
GET |
/__mock/projects |
list hosted projects |
POST |
/__mock/projects?id=&name= |
add a project (body = an OpenAPI spec) → 201 + Location: /<id> |
GET |
/__mock/projects/<id> |
one project's descriptor |
DELETE |
/__mock/projects/<id> |
remove a project |
A single spec keeps the flat routes (/<entity>, /__mock/*). (Durable, multi-team-tenant hosting is a separate plane, not part of this local CLI.)
Relational inference
Relations between schemas are detected by a heuristic, in descending priority: explicit x-relation → $ref → single-ref anyOf/oneOf → …Id/…Sid/…Gid suffix → bare entity name. Ambiguous edges (e.g. a polymorphic anyOf of two entities) abstain rather than guess — they show up as UNRESOLVED in mock graph and can be promoted with an x-relation hint.
$ mock graph examples/demo-spec.yaml
Shop API (5 entities, 5 relations, 0 unresolved)
├─ User
├─ Order
│ ├─ items → OrderItem (many)
│ └─ userId → User (one)
├─ OrderItem
│ ├─ orderId → Order (one)
│ └─ product → Product (one)
├─ Product
│ └─ category → Category (one)
└─ Category
Add --json for a machine-readable graph.
For a richer example, examples/tracker-spec.yaml (a project-tracker SaaS) exercises deep chains, x-relation role hints (assignee/reporter/lead all → User), a self-reference (Issue.parentId → Issue), a many-to-many (Issue.labels → Label), and a polymorphic edge the engine abstains on:
$ mock graph examples/tracker-spec.yaml
Tracker API (8 entities, 16 relations, 1 unresolved)
…
├─ Issue
│ ├─ assigneeId → User (one)
│ ├─ labels → Label (many)
│ ├─ linkedTo → ? UNRESOLVED [candidates: Issue, Milestone]
│ ├─ parentId → Issue (one)
…
CLI
mock dump <spec> [--count N] [--out FILE] Print materialized fixtures (JSON)
mock graph <spec> [--json] Print the inferred relation graph
mock serve <spec...> [--port N] [--host H] [--count N] Run the mock server (1 spec, or N → multi-project)
mock demo [--port N] [--host H] [--count N] Run the server on the bundled demo spec
mock version Print the version
mock completion <shell> Shell completion (bash/zsh/fish/powershell)
mock help Show help (also: any command --help)
Built on spf13/cobra, so every command has its
own --help and shell completion is generated for free.
<spec> is a path to an OpenAPI 3.1 YAML/JSON file, or - to read stdin. Data is deterministic — the same spec and --count always produce the same rows. --host defaults to 127.0.0.1; use 0.0.0.0 to accept external connections (e.g. in Docker).
Fault injection (serve / demo)
Fake slow or failing APIs to test your client's resilience. Configure at boot or live via POST /__mock/injection.
--latency <ms> global added latency (non-negative integer)
--error-rate <0..1> global probability a request is failed
--error-status <400..599> status for injected errors (default 503)
--seed <int> seed the error-rate RNG (reproducible)
--latency-for 'KEY=ms' per-endpoint latency (repeatable)
--error-rate-for 'KEY=0..1' per-endpoint error rate (repeatable)
KEY is METHOD:/Entity or METHOD:/Entity/:id (e.g. GET:/User); copy from GET /__mock/routes. Injected responses carry the header X-Mock-Injected: error.
Scenario snapshots (serve / demo)
Save, share, and replay store state. Snapshots are validated on load (format version + spec fingerprint + FK integrity) and rejected atomically — a bad snapshot never half-loads.
--scenario save|load save state on shutdown, or load before serving
--scenario-file FILE snapshot file path (single-spec --scenario)
--scenario-dir DIR per-project snapshot directory (multi-spec --scenario)
Single spec → one snapshot file. Two or more specs (multi-project) → a directory holding one {project-id}.json per project; load restores each project from its matching file and skips any that's absent. Mixing --scenario-file with multiple specs (or --scenario-dir with one) is a usage error.
# save all projects on shutdown, reload them next run
mock serve api-a.yaml api-b.yaml --scenario save --scenario-dir ./snap
mock serve api-a.yaml api-b.yaml --scenario load --scenario-dir ./snap
Also live: POST /__mock/scenario/{save,load} (per project under /{id}/__mock/... in multi-project mode).
Docker
# bundled demo on :8080
docker build -t mock-server .
docker run -p 8080:8080 mock-server
# your own spec
docker run -p 8080:8080 -v "$PWD/openapi.yaml:/spec.yaml" \
mock-server serve /spec.yaml --port 8080 --host 0.0.0.0
Published images: ghcr.io/thiszona/mock-plane (multi-arch: amd64 + arm64).
Layout
| Package |
Role |
internal/engine |
pure core — spec → entity model → relation inference → materialized, FK-consistent in-memory store. No FS, no HTTP. |
internal/server |
go-chi/chi two-plane HTTP server over a store. |
cmd/mock |
the mock command — dump, graph, serve, demo. |
OpenAPI parsing uses pb33f/libopenapi for real 3.1 support. The engine is deliberately pure so it can back a CLI, a server, a UI, or a hosted service behind the same StateStore seam.
Development
go build ./...
go vet ./...
go test -race -cover ./...
go run ./cmd/mock demo # run locally
Requires Go ≥ 1.26. CI builds, vets, and tests with the race detector, and verifies the Docker image builds.
License
MIT