English | 简体中文
fakeserver is a configuration-driven HTTP mock/fake server for frontend integration, failure-state simulation, local substitutes for unavailable services, and request inspection.
It is distributed as a single Go binary. The current implementation provides:
- An httpbin-style echo service when no route matches.
- JSON5 configuration with mock routes, status codes, headers, delays, response bodies, and
bodyFilefixtures. - Go templates with request context, environment variables, random/time/UUID/Faker helpers, and type-preserving
jsonValuevalues. cases, matching strategies, and named scenarios for switching business states.- Per-route reverse proxying, so mocked and real APIs can run together.
- Declarative pagination (
paginate) that slices a list in the response body by the page requested. - Optional JSONL request-history persistence, with request/response bodies when asked for.
- Hot reload with filesystem notifications and polling fallback.
- A built-in Web UI, request history ring buffer, route replay, scenario controls, and request/reload SSE events.
- Project registration through
listanduse, pluscheckanddoctordiagnostics.
For local development:
go run ./cmd/fakeserver --helpThe recommended installation uses the Makefile. It embeds the version, commit, and build time; upx is used only when available:
make installAlternatively install with Go:
go install ./cmd/fakeserverWithout Makefile ldflags, version metadata is obtained from Go build information and the current Git checkout when available.
Build the current platform binary with either:
go build -o fakeserver ./cmd/fakeserver
make buildGenerate a complete frontend-oriented example in the project directory:
fakeserver init --fullThis creates fakeserver.json5, fakeserver.env.json5, and a .fakeserver/ directory containing route and fixture examples.
Validate and diagnose the project:
fakeserver doctor -c fakeserver.json5 --env dev
fakeserver check --strict -c fakeserver.json5Start the server:
fakeserver serve -c fakeserver.json5 --env devThe default port is 5090. When the admin UI is enabled, open http://127.0.0.1:5090/__fakeserver/ui/. Example requests:
curl http://127.0.0.1:5090/ping
curl http://127.0.0.1:5090/api/users
curl "http://127.0.0.1:5090/api/users?empty=1"check Load and validate a config; --strict also checks common template risks.
doctor Diagnose config, env/include/bodyFile, port, proxy, and admin exposure risks.
init Generate starter files; supports --with-env, --full, and --force.
list List registered projects and their running/idle status.
routes Print a route summary without starting the server.
serve Start the HTTP server.
use Mark a registered project as the most recently used project.
version Print version information; --json emits machine-readable fields.
Useful serve options:
-c, --config: comma-separated config paths; when omitted, the current directory is searched.-e, --env: environment segment fromfakeserver.env.json5.-p, --portand--host: overrideserver.portandserver.host(defaults are5090and0.0.0.0). Restart after changing the effective listen address.--var key=value: repeatable environment-variable override; comma-separated values are accepted.--scenario: default scenario for this process.--no-watch: disable hot reload (the default watcher combines fsnotify with one-second polling).--no-cors: disable CORS middleware.-q, --quiet: suppress request access logs.--history-file <path>: append every request to this JSONL file (overridesserver.historyFile; opened for append, never truncated).--history-body: also record request/response bodies in that file (overridesserver.historyBody).
fakeserver version [--json]fakeserver version prints the same text as --version; version --json prints {"version","commit","buildTime","goVersion"}.
Minimal example:
{
server: { host: "127.0.0.1", port: 5090, cors: true },
routes: [
{ method: "GET", path: "/ping", body: "pong" },
{
method: "GET",
path: "/users/{id}",
headers: { "Content-Type": "application/json; charset=utf-8" },
body: {
id: "{{ .request.params.id }}",
name: "{{ fakeName }}",
requestId: "{{ uuid }}",
},
},
],
}Routes may be declared inline or included with @path/to/file.json5; include paths are relative to the containing config file. bodyFile paths are relative to the route file.
check --strict validates route-level and case-level bodyFile targets; doctor reports missing case-level bodyFile too.
Top-level fields include:
server: listen settings, CORS and access logging, request-body limits, admin/UI, history (historySize,historyFile,historyBody,historyBodyMaxSize), capture, and the default scenario.globals: values available to templates.routes: mock and proxy route declarations.scenarios: named mappings fromMETHOD /pathto a route case.fallback: behavior when no route matches:echo(the default),404, or an object withstatus,headers, andbody/bodyFile(status defaults to 404). Every fallback response has anX-Fakeserver-Fallbackheader.
For an internal-service substitute, return a consistent error object:
fallback: {
status: 404,
body: {
data: null,
status: 404,
code: 404,
message: "fakeserver: no route for {{ .request.method }} {{ .request.path }}",
},
}Cases select different responses on the same route:
{
method: "GET", path: "/api/users", strategy: "first-match",
cases: [
{ name: "empty", when: "request.query.empty == \"1\"", status: 200, body: { items: [] } },
{ name: "server-error", when: "request.query.fail == \"1\"", status: 500, body: { error: "failed" } },
{ name: "success", status: 200, body: { items: [{ id: "u-1", name: "{{ fakeName }}" }] } },
],
}Scenario precedence is X-Fakeserver-Scenario header, Web UI selection, --scenario, server.scenario, then route strategy. A single request can override it:
curl -H "X-Fakeserver-Scenario: serverErrors" http://127.0.0.1:5090/api/userswhen expressions:
check(with or without--strict) precompiles everywhen; syntax errors are reported with the route (METHOD /path) and the case name.- A missing field (expr yields
nil) is a normal non-match, not an error. - A runtime evaluation failure (type error / evaluator error) still degrades to "no match", but is no longer silent: the access log line gains
when_error=<case>:<reason>, the history entry recordswhenError(visible in the Web UI request detail), and the 500 body returned when no case matches lists each case underunmatched(names only) andwhenErrors(with reasons).
Proxy routes forward selected methods to a real backend. proxy is mutually exclusive with mock response fields such as body, bodyFile, cases, status, headers, and
{
method: ["GET", "POST"], path: "/proxy/httpbin/*rest",
proxy: {
target: "https://httpbin.org",
stripPathPrefix: "/proxy/httpbin",
timeout: "10s",
headers: { "X-Fakeserver-Trace": "{{ uuid }}" },
responseHeaders: { "X-Served-By": "fakeserver-proxy" },
},
}paginate makes fakeserver slice a list inside the response body by the page requested:
{
method: "POST", path: "/mes-order/device-task",
paginate: {
pageField: "current", // page field in the request body/query (default "current")
sizeField: "size", // page-size field in the request body/query (default "size")
listPath: "data.list", // required: dot path of the list inside the response body
totalPath: "data.total" // optional: receives the pre-slice length
},
body: { data: { current: 1, size: 50, total: 0, list: [/* full list */] }, status: 200, code: 0 },
}- The page comes from the request body first, then the query string; a missing (or oversized) page size returns the whole list as one page.
- Out-of-range pages return an empty list;
totalPathis filled only where it already exists. - Combined with
cases, the case is selected first and then paginated; a case inherits the route-levelpaginateunless it declares its own. - The list may come from
bodyor frombodyFile. - An unresolvable
listPath(or a non-JSON body) returns 500 with apaginate errorinstead of silently serving unpaginated data.
Beyond the in-memory history, every request can be appended to a JSONL file:
server: {
historyFile: ".fakeserver/history.jsonl",
historyBody: false, // true also records request/response bodies
historyBodyMaxSize: "64KiB", // per-body cap, default 64KiB
}--history-file <path> and --history-body override these (CLI wins). The file is opened for append and never truncated; each line is one JSON object with the same fields as the Web UI history entry (time, method, path, status, duration, client, matched route/case, proxy target, whenError). With historyBody the line also carries request/response bodies (flagged truncated: true past the cap); Authorization, Cookie, X-*-Key, and X-*-Token headers are always redacted. Write failures only warn and never affect serving.
Changing
server.historyFileat runtime requires a restart.
fakeserver.env.json5 holds named environment segments. Selection precedence is --env, FAKESERVER_ENV, $active, then the first non-$default segment. Templates access values through .env; operating-system variables are available through .osenv.
Useful context fields are .request.method, .request.path, .request.params, .request.query, .request.headers, .request.body, .env, .osenv, and .config. Header names containing hyphens use index, for example {{ index .request.headers "User-Agent" }}.
Common helpers include uuid, shortid, now, randInt, randString, fakeName, fakeEmail, fakeCity, and fake.
In JSON bodies, jsonValue preserves the original JSON type when the field consists of one complete action, for example {{ jsonValue .request.body.id }} keeps a numeric ID numeric. Mixed text remains a string; headers render JSON text. toJson and fromJson produce strings in normal template rendering.
When server.adminEnabled is true, fakeserver mounts:
/__fakeserver/ui/
/__fakeserver/healthz
/__fakeserver/routes
/__fakeserver/events
/__fakeserver/api/projects
/__fakeserver/api/config
/__fakeserver/api/history
/__fakeserver/api/history/{id}
/__fakeserver/api/scenario
Admin and UI requests are restricted to loopback clients by default. GET /__fakeserver/healthz remains reachable from any source. To open the UI from another machine or from a container through a port mapping, set server.adminAllowRemote: true. If the server listens on a non-loopback address and remote admin access is enabled, fakeserver prints a warning. Keep adminEnabled disabled or bind to 127.0.0.1 for local-only use.
The UI displays routes, config, projects, and request history; shows matched route/case, proxy details, and when evaluation errors; copies and replays curl requests; tests routes; and changes the selected scenario or per-route case override.
go test ./...
go vet ./...
go build -o fakeserver ./cmd/fakeservermake check runs formatting validation, go vet, and tests. Main packages are internal/cli, internal/config, internal/mock, internal/proxy, internal/tpl, internal/middleware, internal/recorder, and internal/webui. See docs/usage/frontend-workflow.md for a frontend integration workflow.