Skip to content
Pastalikek65Public

About

Mock API Engine — single-binary, dependency-free REST mock server (C++17, epoll, SQLite, JSON/YAML config, hot-reload)

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

mae — Mock API Engine

A single-binary, runtime-dependency-free mock API server. Point it at a JSON or YAML config file and it serves REST endpoints with fake data, configurable latency, per-endpoint rate limiting, hot-reload, and schema-less SQLite CRUD emulation. Built for constrained environments: ~3.8 MiB idle RSS on ARM64, no JVM/Node/.NET, no shared-library runtime deps.

  • Tiny: C++17, epoll event loop, vendored header-only deps (RapidJSON, rapidyaml amalgamation, SQLite amalgamation).
  • Fast: O(log n) trie routing, O(1) token-bucket rate limiting, non-blocking latency timers.
  • Live-reloading: edits to the config file swap routes atomically with zero downtime; a bad edit keeps the previous routes and logs the error.
  • CRUD out of the box: persist_to_sqlite: true turns POST/GET/PUT/DELETE into insert/select/update/delete over an in-memory SQLite table.
  • No runtime deps: one static-ish binary; nothing to install at run time.

Build

Requirements: CMake ≥ 3.16, a C++17 compiler, Ninja (or Make).

cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build build
ctest --test-dir build --output-on-failure     # unit tests (8 suites)
bash scripts/test_api.sh                       # end-to-end curl tests

Termux + PRoot Debian (ARM64)

pkg install proot-distro
proot-distro install debian
proot-distro login debian
apt update && apt install -y build-essential cmake ninja-build curl

Then build as above from the repository directory.

Usage

./build/mae examples/configs/basic.json
./build/mae examples/configs/crud-with-db.yaml   # YAML is supported too
./build/mae --version

The server prints listening on :<port> (N endpoints) and runs until SIGINT/SIGTERM.

Config schema

Format is auto-detected from the file extension (.json, .yaml, .yml).

Field Type Default Description
port int 8080 Listen port (1–65535).
endpoints array [] Route table.

Each endpoint:

Field Type Default Description
method string — (required) GET/POST/PUT/PATCH/DELETE/HEAD/OPTIONS (case-insensitive).
path string — (required) Route, e.g. /users/:id; must start with /. :name segments become path params.
latency.min_ms int 0 Minimum simulated delay.
latency.max_ms int 0 Maximum simulated delay. min == max → fixed; 0 → no delay.
rate_limit.requests int 0 Max requests per window; 0 → unlimited.
rate_limit.window_ms int 1000 Rate-limit window. Exceeding it returns 429 + Retry-After.
persist_to_sqlite bool false Enable in-memory SQLite CRUD for this endpoint.
sqlite_table string derived Table name; if empty it is derived from the path (/users/:id → users).
responses array [{200, application/json, ""}] Response variants; the first is served.

Each response:

Field Type Default Description
status_code int 200 HTTP status (100–599).
content_type string application/json Content-Type header.
body_template string "" Response body template (see below).

Template tokens

Token Renders
{{faker.name}} Random First Last
{{faker.email}} first.last@example.net
{{faker.uuid}} Hyphenated UUID v4-shaped string
{{faker.integer}} Integer 0–1000
{{faker.integer:min:max}} Uniform integer in [min, max]
{{req.param.id}} Path param value (from :id segment)
{{req.query.x}} Query-string value (?x=...)
{{req.body.field}} Field from the JSON request body (dot-path: {{req.body.profile.age}})
{{sqlite.last_insert_id}} Last inserted row id (after a POST to a persisted endpoint)

Unresolved or malformed tokens are left verbatim.

SQLite CRUD behaviour

When persist_to_sqlite is set, method maps to operation on sqlite_table:

Method Operation
POST Insert body; response can echo {{sqlite.last_insert_id}}.
GET With :id → single row; without → all rows as a JSON array.
PUT / PATCH Update the row at :id.
DELETE Delete the row at :id.

The database is in-memory and is kept across hot-reloads (data survives a config edit), but not across process restarts.

Hot reload

While running, editing the config file is detected via inotify. A valid change swaps the route table atomically — in-flight connections and SQLite data are preserved. An invalid change is logged (config reload rejected: ...) and the previous routes keep serving.

Note: editors that replace the file via rename (mv tmp config) are handled on the first rename, but a file-inode watch can go silent after that; in-place edits (the common case) are always detected.

Memory profile

Measured on this build (aarch64, Release, idle + a couple of requests):

$ awk '/VmRSS/ {print $2/1024 " MiB"}' /proc/<pid>/status
3.75 MiB

The idle RSS stays well under the 10 MiB target because the process keeps no GC/VM, holds no per-connection threads, and frees connection buffers on close.

Project layout

include/mae/
  config/   config IR + JSON/YAML loader      net/      epoll loop + HTTP parser
  router/   trie router + rate limiter        db/       sqlite CRUD handler
  mock/     template engine + latency sim     watcher/  inotify config watcher
  server/   server orchestration              util/     logging
src/        implementations                   tests/    ctest executables
scripts/    test_api.sh, fetch_third_party.sh examples/ sample configs

Example

./build/mae examples/configs/basic.json &
curl localhost:8080/health
curl localhost:8080/users/42          # {"id":42,"name":"...","email":"..."}
curl localhost:8080/slow              # delayed ~200-400ms
# 4 rapid requests to /limited -> 200,200,200,429

About

Mock API Engine — single-binary, dependency-free REST mock server (C++17, epoll, SQLite, JSON/YAML config, hot-reload)

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages