DEV Community

Cover image for I built a blazing-fast OpenAPI mock server in Rust — starts in milliseconds, zero config, dynamic fake data
Haowang
Haowang

Posted on

I built a blazing-fast OpenAPI mock server in Rust — starts in milliseconds, zero config, dynamic fake data

Hey HN,

Most OpenAPI mock servers I've worked with fall into one of three buckets: they're slow to boot (JVM warmup, Node module resolution), they need Docker just to run, or they return the same hardcoded stub on every request. None of that is great when you just want to unblock frontend work or sketch out an integration against a spec that's still moving.

So I built mock-cli — a single static binary written in Rust that reads any OpenAPI 3 spec (YAML or JSON) and serves schema-aware, dynamic fake data over HTTP. No runtime, no container, no config file.

Repo: https://github.com/wanghao12345/mock-cli
npm: npm install -g @mock-cli/server

What makes it different

  • Native Rust binary. Cold start is in the low milliseconds. No JVM, no Node overhead, no Docker pull. You run it, it serves.
  • Dynamic, schema-compliant data — not static stubs. Every request produces different fake data that still honors your schema: format: email gives you a realistic email, format: uuid gives you a v4 UUID, enum picks a valid value, required fields are always present, minItems/maxItems are respected.
  • Path-aware. If your route is /pets/{petId} and the response schema has a petId field, the value from the URL is injected into the response (coerced to the schema type). So GET /pets/123 returns {"petId": 123, ...} — consistent with the request context, not random noise.
  • Cycle-safe $ref resolution. Recursive schemas are bounded so the server never blows the stack on a self-referencing model.
  • Correct status codes out of the box. 200 on success, 404 for unknown paths, 405 for unsupported methods, 501 when there's no 200 response defined. Your client's error handling actually gets exercised.
  • All HTTP methods wired up. GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD, TRACE.
  • Single binary, cross-platform. macOS / Linux / Windows, x64 and arm64. Install via npm (auto-picks the right prebuilt binary) or grab the binary from GitHub Releases. Works offline.

30-second demo

Drop a spec:

openapi: 3.0.3
info:
  title: Petstore
  version: 1.0.0
paths:
  /pets/{petId}:
    get:
      parameters:
        - name: petId
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  petId:
                    type: integer
                  name:
                    type: string
                  vaccinated:
                    type: boolean
Enter fullscreen mode Exit fullscreen mode

Start the server:

mock-cli swagger.yaml
# ✓ Loaded "swagger.yaml" (1 paths)
# ✓ Listening on 127.0.0.1:3333
Enter fullscreen mode Exit fullscreen mode

Hit it:

curl http://localhost:3333/pets/123
# {"petId":123,"name":"Dr. Margret Kihn","vaccinated":true}

curl http://localhost:3333/pets/456
# {"petId":456,"name":"Sarah Connor","vaccinated":false}
Enter fullscreen mode Exit fullscreen mode

Notice petId echoes the path param, but name and vaccinated are fresh fake data on every call. That's the whole point — your frontend gets a realistic, varying response shape without you hand-writing fixtures.

Install

# npm (recommended — picks the right prebuilt binary for your platform)
npm install -g @mock-cli/server

# or build from source
cargo install --path crates/cli

# or just run it without installing
cargo run --release -- examples/swagger.yaml
Enter fullscreen mode Exit fullscreen mode

Why Rust?

Honestly, because I wanted a tool I could ship as one static binary with no runtime dependency, and because the OpenAPI spec is a big tagged-union-shaped data model that maps cleanly onto Rust enums. The parsing and generation core (crates/core) has no IO — it's pure data transformation — which makes it trivial to test and reuse. The HTTP layer (crates/server) is axum, and the CLI (crates/cli) is clap. Clean three-crate split.

Roadmap

Done: dynamic mocking, path param injection, schema format/enum/required/array bounds, $ref resolution with cycle safety, correct HTTP status codes, configurable bind host, graceful shutdown.

Planned: hot reload on spec change, request validation against the spec, custom faker rules via config, multi-spec composition.

Try it

npm install -g @mock-cli/server
mock-cli examples/swagger.yaml
Enter fullscreen mode Exit fullscreen mode

Repo + full docs: https://github.com/wanghao12345/mock-cli

Happy to hear feedback — especially on the faker behavior (what formats/enums you'd like better coverage for) and whether hot-reload or request validation should land first. Issue threads are open.

MIT licensed. Written in Rust 1.85+.

Top comments (0)