Developer ToolsBackend Simulation

Mock API Server

Frontend and backend development often need to happen in parallel. The Mock API Server lets you define fake endpoints with custom responses so your frontend code has something to work against before the real API exists.

May 24, 20268 min read
🔧

Mock API Server

Fake REST endpoints — no Node.js, no backend needed.

What is a mock API and why do developers use one?

A mock API is a simulated HTTP server that returns pre-defined responses to HTTP requests without executing real business logic or touching a database. Instead of pointing your frontend at a live backend, you point it at the mock, which replies with whatever JSON you configure. From the perspective of your JavaScript code, the response is indistinguishable from a real one — it arrives over the same fetch call, carries the same HTTP status code, and contains the same JSON structure.

The most common reason to use a mock API is parallel development. In a typical product team, the backend engineers and frontend engineers agree on a contract — the shape of the request and the shape of the response — and then split off to build their respective sides simultaneously. Without a mock, the frontend team is blocked until the backend endpoints are ready and deployed to a shared environment. A mock API eliminates that dependency entirely. Frontend engineers can build, wire up, and iterate on their UI without waiting for a single line of backend code to exist.

The second major use case is testing edge casesthat are difficult or impossible to reproduce against a real backend. What happens in your UI when the server returns a 500 error? What does your loading skeleton look like when the API takes three seconds to respond? What does the empty-state component render when the API returns an empty array? With a real backend you'd need to engineer special conditions to trigger each of these scenarios. With a mock, you define them declaratively and switch between them instantly.

A third use case is demos and prototypes. If you want to show a stakeholder or client how a feature will look before any backend infrastructure exists, a mock API lets you wire up a fully interactive prototype in hours rather than days. The demo experience is indistinguishable from a real integration.

Finally, mock APIs are valuable for reliable test data. Real APIs are stateful — running a test suite against a live database can leave behind side effects that cause subsequent runs to fail. A mock always returns exactly what you configured, making your test suite deterministic and reproducible regardless of the order in which tests run.

How browser-based mocking works

Traditional mock servers require running a Node process (json-server, Express, etc.) on your machine. This tool uses a Service Worker to intercept fetch requests from the same browser tab. When your code calls fetch('/api/users'), the Service Worker matches it against your configured routes and returns the response — no actual network request happens.

Service Workers are a browser-native technology that sit between your web page and the network. Originally designed for offline Progressive Web Apps, they can intercept any outgoing fetch request and respond to it programmatically. This means the mock runs entirely inside your browser — there is no server process, no port to configure, and no firewall rules to worry about. The Service Worker code is sandboxed to its origin and cannot affect other tabs or other websites.

When a route does not match any configured mock, the request is passed through to the real network. This is important: it means you can mock only the endpoints you care about and let everything else — your CDN assets, analytics, third-party scripts — continue to work normally.

Request flow

Your codefetch('/api/users')
Service WorkerIntercepts the request, checks route table
Route match foundReturns configured JSON response + status code
No matchPasses through to real network (configurable)

Defining mock routes

Each mock route has five fields: method, path, status code, response body (JSON), and optional delay. You can add as many routes as you need.

The method field accepts any standard HTTP verb — GET, POST, PUT, PATCH, DELETE, HEAD, or OPTIONS. The path supports static segments such as /api/users and parameterised segments such as /api/users/:id. When using a parameterised segment, the mock will match any request that fits the pattern regardless of what the actual :id value is — useful for simulating both a successful look-up and a not-found case on the same endpoint type.

The response body is a JSON editor. You type (or paste) the exact JSON object or array you want the mock to return. The editor validates the JSON on every keystroke and highlights syntax errors before you save. Here is an example response body for a user list endpoint:

{
  "data": [
    { "id": 1, "name": "Alice Chen", "role": "admin", "active": true },
    { "id": 2, "name": "Bob Patel", "role": "editor", "active": true },
    { "id": 3, "name": "Carol Kim",  "role": "viewer", "active": false }
  ],
  "meta": {
    "total": 3,
    "page": 1,
    "per_page": 20
  }
}

Notice the meta wrapper — it reflects the envelope structure many real APIs use for paginated responses. By matching your mock data to the shape your real API will eventually return, you ensure that all the frontend code you write against the mock will work without changes once the real backend is ready.

Example route configuration

MethodPathStatusDelayPurpose
GET/api/users2000msReturn user list
POST/api/login200300msSimulate auth with realistic delay
GET/api/users/14040msTest 404 handling
DELETE/api/items/:id500100msTest server error UI

Simulating status codes

One of the most powerful aspects of a mock API is the ability to return any HTTP status code on demand. Real backends rarely produce certain error codes in a way that's easy to trigger repeatedly during development — a 503 Service Unavailable, for example, would require the real server to be degraded. With a mock you can pin a route to any status code and test your UI's response in seconds.

The commonly useful status codes and what to test with each:

200 OK

Standard success. Use for the happy path.

201 Created

POST endpoints that create a resource. Tests that your UI shows a success confirmation.

400 Bad Request

Invalid input. Tests that your form validation error messages render correctly.

401 Unauthorized

Missing or invalid credentials. Tests that your app redirects to the login page.

403 Forbidden

Authenticated but lacking permission. Tests role-based access control in the UI.

404 Not Found

Resource does not exist. Tests your not-found component or empty state.

422 Unprocessable Entity

Server-side validation failed. Return field-level error messages in the body.

500 Internal Server Error

Unexpected server crash. Tests your generic error boundary or retry mechanism.

A particularly useful pattern is defining two routes with the same method and path — one returning 200 and one returning 500 — and toggling between them. This lets you quickly flip a route between the happy path and the error state without re-editing the JSON body each time.

Simulating real-world conditions: latency and delays

A mock that responds instantly every time is not realistic. Real networks have variable latency — a mobile user on a congested 4G network might experience 400-800ms of round-trip time for a simple API call. If you develop entirely against instant responses, you may ship a UI that has no loading states, freezes during slow fetches, or shows jarring content jumps when data loads too quickly for the eye to track.

The delay field on each route solves this. Set it to any value in milliseconds and the Service Worker will wait that long before returning the response. Common useful delay values:

0 ms

Instant — good for unit test scenarios where you care only about data, not timing.

100–300 ms

Fast broadband or LAN. Tests that your loading skeleton appears but disappears quickly.

400–800 ms

Typical 4G mobile. The most realistic delay for consumer-facing products.

1000–2000 ms

Slow 3G or high server load. Stresses your timeout UX and progress indicators.

3000+ ms

Near-timeout territory. Useful for testing cancellation, retry buttons, and timeout error messages.

Delays are set per-route, so you can mix fast and slow responses in the same session — for example, a fast user profile load combined with a slow analytics summary that simulates an expensive aggregation query. This allows you to build and test skeleton loaders that appear independently as each piece of data arrives.

⏱️

Response delay

Add 100-2000ms delays to simulate network latency and test loading states in your UI.

💥

Error status codes

Return 400, 401, 403, 404, 422, 500, 503 to test every error handling path in your frontend.

📦

JSON templates

Use preset JSON templates for common patterns (user objects, paginated lists, error envelopes) to save time.

Typical development workflows

Understanding when to bring in a mock API — and how to structure your work around it — makes a bigger difference than any individual feature. Here are the patterns that work best in practice.

Contract-first parallel development

At the start of a feature, the frontend and backend engineers meet for fifteen minutes to agree on the API contract: the HTTP method, the URL path, the request body shape, and the response body shape. This agreement is written down — ideally as an OpenAPI snippet or even just a JSON example in a shared doc. The frontend engineer then creates a mock route that matches the agreed contract and starts building the UI immediately. The backend engineer writes the real implementation. When the backend lands, swapping from mock to real requires changing one base URL — nothing else.

Systematic error state coverage

Before shipping a feature, go through every API call it makes and define a mock variant for each error code the real API can return. Flip to each variant in turn and manually verify that the UI handles it gracefully: does the 401 route to the login page? Does the 422 surface the right field-level error? Does the 500 show a user-friendly message rather than a blank screen? This systematic sweep is far more efficient than trying to reproduce these conditions in a real environment.

Demo and stakeholder review

When preparing a demo, configure mocks that return polished, realistic-looking data — proper names, sensible numbers, plausible dates. Disable any routes that would produce loading spinners or empty states unless the demo specifically covers those. Because the mock is defined per-tab, you can have a "demo mode" set of routes open in one window and your development configuration in another.

Automated testing with deterministic data

When writing end-to-end tests with tools like Playwright or Cypress, you want the same data every time so assertions do not flake. Export your mock route configuration as JSON and commit it to your repository. In CI, the test runner loads the mock configuration before each test run, guaranteeing that every fetch call returns exactly the expected data regardless of the state of the real backend.

Building realistic mock responses

The quality of your mock data directly affects how useful the mock is. Minimal placeholder data — single-character strings, sequential IDs, all-zero timestamps — makes it harder to spot layout bugs in your UI and gives an unrealistic picture during demos. Invest a few extra minutes in crafting realistic JSON.

Here are a few patterns worth following:

1.

Match production field names exactly. If the real API returns created_at not createdAt, use the snake_case version in your mock. Mismatched casing is one of the most common sources of silent breakage when transitioning from mock to real.

2.

Include optional and nullable fields. If the real API sometimes returns null for a field, create two mock variants — one where the field is populated and one where it is null — and test both.

3.

Include boundary values. Put one item in the list, put hundreds of items in the list, use a very long string value, use an empty string. These extremes expose layout overflow bugs and missing truncation that normal test data never reveals.

4.

Use ISO 8601 timestamps. Many real APIs return dates in ISO format such as "2026-05-24T14:32:00Z". Using that exact format in your mock ensures your date parsing and formatting code is exercised correctly.

For error responses, include a body that matches what the real API will return. Many APIs wrap errors in a standard envelope — for example:

{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "The email address is already in use.",
    "fields": {
      "email": ["Email has already been taken"]
    }
  }
}

Pairing the mock server with the API client

The mock API server and the API client tool complement each other naturally. Use the API client to send a request to your mock route and inspect the exact JSON body, headers, and status code it returns. This makes it easy to spot typos in your JSON or misconfigured status codes before you wire them up in your frontend application code.

A common workflow is to open the mock server in one browser tab, define your routes, then switch to the API client tab and fire requests at the same mock origin to verify responses. Because both tools run in the same browser and the Service Worker is scoped to the origin, requests from the API client are intercepted by the mock exactly as they would be from your application code.

Frequently Asked Questions

How does a browser-based mock server work without Node.js?

The mock server uses a Service Worker to intercept fetch requests from the same browser tab. When your frontend code calls fetch('/api/users'), the Service Worker matches it against your defined routes and returns the configured response — no network request leaves the browser.

Can I test error handling with the mock server?

Yes. Set any route to return a 4xx or 5xx status code. You can also simulate intermittent errors by defining the same route twice with different status codes and toggling between them.

Does it work with frameworks like React or Vue?

Yes. Any fetch or axios call from the same origin tab will be intercepted. This includes calls from React Query, SWR, or any other data-fetching library.

Can I persist my mock routes between browser sessions?

Yes. Routes are saved to localStorage automatically so they survive page refreshes. You can also export your route configuration as JSON and import it later to restore your entire mock setup in seconds.

How do I simulate a slow network or timeout?

Set the delay field on any route to a value in milliseconds. For a typical 3G network feel use 300-600ms. To simulate a near-timeout, use a large delay such as 5000ms combined with a 408 status code so your frontend timeout handler is triggered.