zapstand-mock
v0.1.0
Published
Local mock server for third-party APIs (uazapi WhatsApp first), with a per-project simulation UI and a global Studio. 100% local, no real connections.
Readme
zapstand
Local mock server for third-party APIs, with a per-project simulation UI and a global Studio. 100% local, no real connections, no Docker/Redis/PM2 required.
The first (and currently only) provider is uazapi — a faithful mock of the
uazapi WhatsApp API: same routes, same payload field
names, same HTTP status codes, and asynchronous webhooks — so you point your
system-under-test at the mock by changing nothing but the BASE_URL.
npm install -g zapstand # once, globally
zapstand app create projeto-x --provider uazapi # one app per project
zapstand start projeto-x # mock API + simulation UI
zapstand studio # manage all apps in one placeConcepts
- Provider — the implementation of one external API to mock (its routes,
payloads, webhooks). v1 ships
uazapi. New providers plug in as independent folders without touching the core — see CONTRIBUTING-PROVIDERS.md. - App — one project under test (e.g.
projeto-x). Each app is bound to a provider at creation and has its own isolated data (SQLite DB + media): instances, contact profiles, conversations. Apps never see each other's data. - Instance (uazapi) — a simulated "connected number". An app can have many.
- Mock Server — the provider's HTTP API, the thing your code calls.
- Simulation UI — a WhatsApp-Web-style panel where you act as an external contact: create fictitious contacts and send them messages to your system, and watch your system's replies arrive in real time.
- Studio — a global admin panel (Prisma-Studio style) listing every app of every provider, with create/remove for apps and instances.
Install
# from npm (once published)
npm install -g zapstand
# or from this repo
npm install
npm run build
npm link # exposes the `zapstand` command globallyRequires Node.js ≥ 18. The only native dependency is better-sqlite3 (compiled
on install).
Daily workflow
# 1. create an app for your project (seeds one connected instance + one contact)
zapstand app create chatbot-clinica --provider uazapi
# 2. start it (FOREGROUND — occupies the terminal, logs here, Ctrl+C to stop)
zapstand start chatbot-clinica
# mock API: http://localhost:4000
# sim UI: http://localhost:4000 (open in the browser)
# 3. point your system-under-test at the mock:
# UAZAPI_BASE_URL=http://localhost:4000
# token: copy it from the sim UI ⚙ Configurações (per instance)
# 4. open the sim UI, send a message as the contact → it arrives at your
# system as a `messages` webhook. Your system replies via /send/text →
# the reply shows up in the chat. Status ticks update live.Run several projects at once by opening one terminal per app (this is the
deliberate, PM2-free model). Or use --detach to background one:
zapstand start projeto-x --detach # writes run.json + logs/out.log
zapstand logs projeto-x # tail those logs
zapstand stop projeto-x # stop the backgrounded processCLI reference
| Command | Description |
|---|---|
| zapstand providers list | List available providers (v1: uazapi). |
| zapstand app create <slug> --provider <name> | Create an app. --name <s>, --no-seed. |
| zapstand app list | List apps with their provider. |
| zapstand app remove <slug> | Remove an app and all its data. |
| zapstand start <slug> | Start mock + UI in the foreground. --port <n>, --delay <ms>, --detach. |
| zapstand stop <slug> | Stop a --detach'd app (foreground apps stop with Ctrl+C). |
| zapstand ps | List apps currently running (via health-check). |
| zapstand logs <slug> | Tail a --detach'd app's logs. -n <lines>. |
| zapstand studio | Open the global Studio. --port <n> (default 4500). |
How "is it running?" is detected
No PID files, no OS signals. Each app exposes GET /__health; zapstand ps
and the Studio probe the app's last-known port (stored in apps.json) with a
short timeout. This works identically for foreground and --detach apps and
never reports a stale process as alive.
There is no auto-restart and no clustering — by design. If an app stops, it
stays stopped until you run zapstand start again.
Storage layout
Everything lives under ZAPSTAND_HOME (default ~/.zapstand):
~/.zapstand/
apps.json # registry: slug, name, provider, createdAt, lastPort
apps/
projeto-x/ # provider: uazapi
db.sqlite # instances, contacts, conversations, messages
media/ # uploaded media for this app
logs/ # out.log / err.log (only when --detach)
run.json # pid/port/startedAt of the current runSet ZAPSTAND_HOME to move all apps at once (external disk, network volume,
etc.). Each app is a self-contained subfolder; creating/removing one never
affects another.
Environment variables
See .env.example. Summary:
| Variable | Default | Purpose |
|---|---|---|
| ZAPSTAND_HOME | ~/.zapstand | Root for all app data. |
| ZAPSTAND_DELAY_MS | 600 | Default webhook/status-transition latency. |
Quality-of-life for testing
- Reset state: the sim UI ⚙ → Resetar estado do app, or
POST http://localhost:<port>/__reset. Clears that app only. - Live request log: every inbound API call and outbound webhook is printed in the terminal and streamed to the sim UI's Logs tab.
- Fault injection: send any
/send/*call with headerx-zapstand-simulate: error | blocked | timeout(or a"simulate"field in the body) to exercise your error handling.
uazapi mock — what's implemented
Routes faithful to the real API (auth header token, destination field
number, message ids id/messageid, status enum Sent/Delivered/Read,
messageTimestamp in milliseconds). Full request/response examples are in
docs/UAZAPI-ROUTES.md:
- Instances:
POST /instance/create,POST /instance/connect,GET /instance/status,POST /instance/disconnect,DELETE /instance,GET /instance/all,POST /instance/presence - Sending:
POST /send/text,/send/media,/send/location,/send/contact,/send/menu - Messages:
POST /message/markread,/message/presence,/message/react - Chats/contacts:
POST /chat/find,POST /message/find,POST /chat/read,GET /contacts,POST /contacts/list,POST /chat/check - Blocking:
POST /chat/block,GET /chat/blocklist - Webhooks:
POST /webhook,GET /webhook; async delivery ofmessages,messages_update,connection,blocks
Webhook envelope caveat. uazapi's public docs do not pin down the exact delivered webhook body. zapstand uses the best-supported format (
{ EventType, event, token, owner, instance, <payload> }); if your real integration expects something different, it's a one-place change insrc/providers/uazapi/webhooks.ts. See the note there and in the routes doc.
Develop
npm run build # builds server (tsc) + both UIs (vite) into dist/
npm run dev:server # tsc --watch for the backend
npm run typecheck # type-check without emitting
# Frontend hot-reload while an app runs on :4000 (uazapi UI):
npx vite --config src/providers/uazapi/ui/vite.config.ts
# Studio hot-reload while `zapstand studio` runs on :4500:
npx vite --config src/web/studio/vite.config.tsLicense
MIT
