mockingfrontendworkflowdevtoolstesting

Building the Frontend Before the Backend Exists — a JSON Mocking Workflow

·8 min read·Developer Tools

The dependency that shouldn't block you

"We can't build the dashboard until the backend team ships the /stats endpoint" is a scheduling problem, not a technical one — the frontend only needs to agree on the *shape* of the JSON it'll receive, not wait for a real implementation to exist. This post covers the workflow, not the data generation itself — see Random JSON Generator if what you need is realistic fake *values*; this is about wiring mocked JSON into your actual dev loop so it behaves like the real API will.

Step 1: Agree on the contract before writing UI code

A mock is only useful if it matches what the backend will eventually return — so the first artifact isn't code, it's a JSON sample both sides sign off on:

json
{
  "revenue": 48213.50,
  "orders": 341,
  "topProducts": [
    { "sku": "SKU-1", "name": "Widget Pro", "unitsSold": 88 }
  ]
}

This can come from an API design doc, an OpenAPI spec's example field, or — fastest in practice — the backend engineer pasting a realistic sample straight from their in-progress work, even before the endpoint is deployed anywhere. JSONKit's Random JSON Generator is useful here too: define the field shapes visually and generate a batch of varied, realistic-looking records to build and test the UI against more than one example, not just the single "happy path" sample from the design doc.

Step 2: Intercept the fetch, don't fork the code

The failure mode to avoid is component code that branches on an environment flag (if (USE_MOCK) return mockData; else return fetch(...)) — that's a permanent maintenance burden and a real chance the mock path silently diverges from the real one. Instead, intercept the *network layer* so component code is identical in both cases.

Mock Service Worker (MSW) intercepts fetch/XMLHttpRequest at the network level, in the browser or in Node for tests:

javascript
// npm install msw
import { http, HttpResponse } from "msw";
import { setupWorker } from "msw/browser";

const worker = setupWorker(
  http.get("/api/stats", () => {
    return HttpResponse.json({
      revenue: 48213.50,
      orders: 341,
      topProducts: [{ sku: "SKU-1", name: "Widget Pro", unitsSold: 88 }],
    });
  }),
);

if (process.env.NODE_ENV === "development") worker.start();

Your component's fetch("/api/stats") call never changes — MSW sits below it, transparently. Flip a flag or an environment variable to disable the worker once the real endpoint exists, and delete the handler once it's no longer needed; nothing in the component tree ever knew the difference.

json-server is the lighter-weight alternative when you want a real, separately-running mock HTTP server rather than an in-browser interceptor — point it at a JSON file and it serves a full REST API (GET/POST/PUT/DELETE, even query filtering) from it in one command:

bash
npm install -g json-server
echo '{ "products": [{ "id": 1, "name": "Widget" }] }' > db.json
json-server --watch db.json
# GET http://localhost:3000/products now works, no backend code written

Step 3: Model the states the real API will have, not just the happy path

The UI bug that ships most often against a mock is one that only handles the empty-array, error, and loading states in theory, because the mock only ever returned the happy path. Build at least three response variants into the mock from the start:

javascript
http.get("/api/stats", () => {
  const scenario = new URL(location.href).searchParams.get("mock");
  if (scenario === "empty") return HttpResponse.json({ revenue: 0, orders: 0, topProducts: [] });
  if (scenario === "error") return HttpResponse.json({ error: "Internal error" }, { status: 500 });
  return HttpResponse.json({ revenue: 48213.50, orders: 341, topProducts: [/* ... */] });
});

A query-param-driven scenario switch like this lets anyone reviewing the UI — a designer, a PM, QA — pull up the empty and error states directly by URL, without needing dev tools open or a debugger breakpoint.

Step 4: Swap to the real API with a config change, not a rewrite

Because the interception happens below the component layer, "swapping to the real backend" should mean flipping one flag or environment variable, not touching component code. If it means more than that, the mock diverged from the real contract's *shape* — usually a field the mock had that the real API doesn't (or vice versa) — and that mismatch is exactly what step 1's shared JSON sample was meant to prevent.

Frequently asked questions

For a small team, colocating them in the frontend repo is simplest. For larger orgs with an OpenAPI spec as the source of truth, generating mocks *from* that spec (several MSW/Prism-based tools do this) keeps the mock from drifting out of sync with the documented contract as it evolves.

Related but distinct — this workflow is about *developing and demoing* against a fake backend interactively in the browser; mocking fetch inside a Jest/Vitest unit test is about isolating a single component's logic from network calls entirely. MSW happens to support both use cases with the same handler definitions, which is part of why it's popular.

This is the main risk of the whole approach — mitigate it with a contract test that runs the real API's response through the same JSON Schema the mock is built from, so a divergence fails CI instead of surfacing as a confusing runtime bug after deployment. See JSON Schema Validation for validating a real response against the schema you designed the mock around.

Yes — once you know the field shapes, JSONKit's Random JSON Generator can generate as many varied records as you need (including linked, multi-table data) directly in the browser, which is a faster way to get realistic breadth (different name lengths, edge-case values, empty vs. populated arrays) than hand-writing three or four examples by hand.

Try JSONKit — JSON Developer Tools

Format, validate, convert and diff JSON. Instant, accurate and 100% private.