Skip to content

Repository files navigation

mock8

A dashboard for frontend devs to stand up fake HTTP endpoints — define a path, paste the JSON you expect back, and call it from your app. No backend team, no waiting for a staging deploy.

  • Mock endpoints — method + path + status + JSON body, editable from the dashboard.
  • Global response headers — sent on every mock response; endpoints can override them.
  • Simulated delay — a global delay, overridable per endpoint, for exercising loading states.
  • Request logs — a live view of incoming traffic with the status that went back.
  • Dynamic responses — echo request values with defaults, and generate fake data with Faker.js.

Getting started

Requires Node 20.9+ and git. One command clones, installs, starts, and opens the dashboard:

curl -fsSL https://raw.githubusercontent.com/thesanjeevsharma/mock8/main/scripts/install.sh | bash

It clones into ./mock8, picks the next free port if 6767 is taken, and leaves the dev server in the foreground — Ctrl+C stops it. Re-run it any time to pull the latest and start again; it skips the reinstall when nothing changed, and won't touch a checkout with uncommitted work.

Pass options after -s --:

curl -fsSL <url>/scripts/install.sh | bash -s -- --dir ~/tools/mock8 --port 7000
Flag Env var Default
--dir PATH MOCK8_DIR ./mock8
--port N PORT 6767
--branch NAME MOCK8_BRANCH main
--no-open MOCK8_OPEN=0 opens
--no-start MOCK8_START=0 starts

Piping a script into bash runs whatever the URL serves. If you'd rather read it first — please do — it's scripts/install.sh, or just do it by hand:

git clone https://github.com/thesanjeevsharma/mock8.git
cd mock8
npm install
npm run dev

Either way, open http://localhost:6767. It redirects to the dashboard and seeds two examples — a plain JSON response and one using request values plus fake data — so there's something working immediately:

curl -i http://localhost:6767/users/me
curl -i -X POST http://localhost:6767/api/v2/signup \
  -H 'Content-Type: application/json' -d '{"email":"you@example.com"}'

No database to provision, no config file to fill in.

Scripts

Command What it does
npm run dev dev server with hot reload on port 6767
npm run build production build
npm start serve the production build (also 6767)
npm run lint ESLint

Environment variables

Both are optional.

Variable Default Effect
PORT 6767 port for dev and start
MOCK8_LOG_RETENTION_MS 900000 how long request logs are kept (15 min)
PORT=4000 npm run dev
MOCK8_LOG_RETENTION_MS=60000 npm run dev   # keep 1 minute of logs

Calling your mocks

Mocks are served straight from the root, so point your frontend's base URL at the origin — no prefix:

http://localhost:6767

A mock defined as GET /api/v1/users answers at GET http://localhost:6767/api/v1/users.

// in your app
const api = axios.create({ baseURL: "http://localhost:6767" });
await api.get("/api/v1/users");

The dashboard gets out of the way under /mock8, which is the only path space a mock can't claim (http://localhost:6767 redirects there so the bare URL still opens something useful). Trying to mock a reserved path is rejected in the dialog rather than silently ignored:

Reserved Why
/ redirects to the dashboard
/mock8/* the dashboard and its own API
/_next/* Next.js internals

Everything else is yours, /api/* included.

CORS is permissive by default (the caller's origin is reflected, credentials are allowed, preflights are answered), so a frontend on any other port can call these from the browser. Turn it off in Settings if you want to test CORS failures.

Path matching

Pattern Matches
/users/me exactly /users/me
/users/:id /users/42, /users/abc
/api/v:version/me /api/v2/me
/files/* /files/a, /files/a/b/c

A :param can take a whole segment or part of one. Query strings are ignored when matching. The most specific match wins — the more literal text a pattern pins down, the higher it ranks, so /orders/latest beats /orders/:id and /api/v:version beats /api/:anything. Matched params come back on the X-Mock8-Path-Params response header.

Dynamic responses

Response bodies are templates. Each helper takes a path or name, plus an optional default as a second argument:

Helper Reads from
{{body 'name.first' 'Anon'}} JSON request body — dot paths and items.0.id
{{pathParam 'id'}} a :param captured by the endpoint's path
{{queryParam 'page' '1'}} query string
{{header 'authorization'}} request header (case-insensitive)
{{cookie 'session' 'none'}} Cookie header
{{faker 'string.uuid'}} generated fake data — see below

Quoting decides the type. Inside quotes you get a string; unquoted you get the real JSON value:

{
  "who":    "{{body 'name.first'}}",   // "Ada"
  "age":    {{body 'age' '0'}},        // 20 — a number, or 0 if absent
  "active": {{body 'active'}},         // true
  "user":   {{body 'name'}},           // {"first":"Ada","last":"Lovelace"}
  "hello":  "Hi {{body 'name.first'}}, page {{queryParam 'page' '1'}}"
}

String values are escaped for the position they land in, so a value containing quotes or newlines can't break your JSON. Use the triple-brace form {{{body 'fragment'}}} to insert a value unescaped when you want it verbatim.

Fake data

{{faker 'namespace.method'}} calls Faker.js, so essentially any namespace works. Arguments come in the documented three forms:

{
  "id":      "{{faker 'string.uuid'}}",
  "country": "{{faker 'location.country'}}",
  "small":   {{faker 'number.int' '100000'}},
  "ranged":  {{faker 'number.int' '{min:500, max:600}'}},
  "price":   {{faker 'number.float' '{fractionDigits:2}'}},
  "when":    "{{faker 'date.future' 'iso'}}",
  "born":    "{{faker 'date.past' 'us'}}"
}

For date.* methods a string second argument is a format and an object is passed to faker. Formats: iso, iso8601, rfc2822, unix, timestamp, short, long, full, compact, utc, sql, us, eu, or your own tokens like YYYY-MM-DDTHH:mm:ss.SSSZ. unix and timestamp come out as numbers.

When something doesn't resolve

A missing value with no default becomes null (empty inside a string) and is listed on the X-Mock8-Unresolved response header and in the Logs tab — a typo shows up instead of silently serving nulls.

A broken template is different: bad syntax is rejected when you save, and a bad faker attribute returns 561 with the reason in the body, so you never mistake it for your mock's own status. Bodies without {{ are served byte-for-byte as authored.

Response precedence

Headers are applied in this order, last one winning:

  1. Content-Type: application/json
  2. CORS headers (if enabled)
  3. Global headers (Settings)
  4. The endpoint's own headers

Delay resolves as endpoint delay ?? global delay, capped at 60s. Every response carries X-Mock8: hit | miss | preflight so you can tell a mock apart from a real backend in devtools.

204, 205, 304 and HEAD requests are sent without a body per spec. A HEAD request with no HEAD mock falls back to the matching GET mock.

Logs

The Logs tab is a live tail of every request that reaches a mock path. The dashboard's own API calls are not logged, so the tab can't fill with its own polling. Each row is recorded once the response is settled, so the status code is the real one that went back:

Column Meaning
Time local wall-clock, to the millisecond
Path as requested, query string included
Status the status actually sent
Outcome hit · miss 404 · preflight CORS · template-error 561
Took total time the caller waited, simulated delay included

Expanding a row shows the pattern that matched, extracted path params, how much of the duration was simulated delay, the calling origin, and the request body (truncated at 1 KB).

Polls every 2s; Live pauses without losing anything, since it resumes from a cursor. Clear empties the buffer immediately.

Logs are in memory, capped at 1000 entries, and entries older than 15 minutes are dropped — both on read and by a sweeper timer. Nothing is written to disk, so restarting the dev server starts a fresh tail. Adjust the window with MOCK8_LOG_RETENTION_MS.

Storage

Endpoints and settings live in data/db.json, created on first run and gitignored. Reads hit the file on every request with no caching, so a hand-edit takes effect immediately — no restart. Writes are queued and atomic, so a crash mid-save can't leave a truncated file.

It's a file, not a database: fine for one dev on localhost, but it won't hold up as a shared service with concurrent writers. Everything storage-related is behind the exported functions in src/lib/db.ts, so swapping it out wouldn't touch the rest of the app.

Dashboard API

The dashboard drives a small JSON API you can script against. It lives under /mock8/api so that a plain /api/* stays yours to mock:

Route Purpose
GET /mock8/api/endpoints list endpoints + settings
POST /mock8/api/endpoints create an endpoint
PATCH /mock8/api/endpoints/:id partial update
DELETE /mock8/api/endpoints/:id delete
GET /mock8/api/settings read global headers / delay / CORS
PUT /mock8/api/settings update settings
GET /mock8/api/logs?since=N request logs after cursor N
DELETE /mock8/api/logs clear the log buffer

Bodies are validated: JSON must parse, status must be 100–599, the path must not be reserved, and method + path must be unique.

curl -X POST http://localhost:6767/mock8/api/endpoints \
  -H 'Content-Type: application/json' \
  -d '{"method":"GET","path":"/api/health","body":"{\"ok\":true}","delayMs":250}'

Day-to-day

Everything lives on three tabs:

  • Endpoints — create, edit, duplicate and delete mocks. The switch in the On column disables a mock without deleting it, which is the quick way to make a route 404 and test your error path. The menu copies a ready-to-paste curl command.
  • Logs — watch traffic as it arrives. Start here when a call isn't behaving: a miss row means no mock matched the method and path you think it did.
  • Settings — global headers, global delay, CORS.

A few things worth knowing:

  • Filtering — both tables filter as you type; Logs also filters by outcome, so you can show only misses.
  • Sharing a set of mocksdata/db.json is a plain file you can send to a teammate or hand-edit; changes are picked up on the next request, no restart. It's gitignored by default, so drop the /data line from .gitignore if you'd rather commit a shared set.
  • Starting over — delete data/db.json and it re-seeds on the next request.

Working on the codebase

src/
  app/
    [...path]/route.ts      the mock server — every request that isn't the dashboard
    page.tsx                / redirects to the dashboard
    mock8/                  the dashboard: pages + its own API under /api
  components/               UI — endpoints-view, logs-view, settings-form are the tabs
  lib/
    db.ts                   JSON-file store, validation, atomic writes
    paths.ts                path normalizing + pattern matching and specificity
    template.ts             template rendering: request helpers + faker (server only)
    template-syntax.ts      template parsing, JSON-aware validate + format (shared)
    logs.ts                 in-memory request log ring buffer
    types.ts                shared types, DASHBOARD_PREFIX, reserved paths
scripts/
  install.sh                the one-line installer

Where to make common changes:

To change… Edit
the dashboard's URL prefix DASHBOARD_PREFIX in src/lib/types.ts (and rename the folder)
path-matching or specificity compileSegment / matchScore in src/lib/paths.ts
template helpers or faker args resolve / callFaker in src/lib/template.ts
template parsing or validation src/lib/template-syntax.ts
validation rules for saving validate in src/lib/db.ts
log retention or buffer size the constants at the top of src/lib/logs.ts

The dashboard folder must not start with an underscore — Next treats _folder as private and silently drops it from routing.

@faker-js/faker must stay out of the client bundle — that's why template parsing lives in template-syntax.ts (imported by the dialog) and rendering in template.ts (server only). Keep faker imports on the server side of that line.

Before pushing: npm run lint && npx tsc --noEmit && npm run build. There's no test suite; paths.ts, template.ts and template-syntax.ts are pure functions and would be the natural place to start one.

Troubleshooting

A call 404s that shouldn't. Check the Logs tab — the miss row shows the exact method and normalized path that was looked up. Usual causes are a method mismatch, a disabled endpoint, or a :param that doesn't line up.

"Another next dev server is already running." Next 16 allows one dev server per directory. Stop the other one: lsof -ti:6767 | xargs kill.

Port already in use. Run on another port with PORT=4000 npm run dev.

Logs are empty after a restart. Expected — they're in memory by design, so a restart starts a fresh tail. Endpoints and settings persist in data/db.json.

CORS errors in the browser. Confirm CORS is on in Settings, and that you're calling the mock origin directly rather than through a proxy that strips headers.

Stack

Next.js (App Router) · TypeScript · Tailwind CSS v4 · shadcn/ui

About

An open source API mocking service.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages