Mocking an API Before the Backend Exists: A Practical Workflow
The frontend is ready to build the order history screen. The /orders endpoint is "about two weeks out." Everyone has lived this. The frontend team can wait, or they can hard-code some JSON into a component and hope it resembles what eventually ships.
A mock API is the better third option: a stand-in server that returns realistic responses for the endpoints you've agreed on, so both sides can build in parallel. Done well, it speeds everyone up. Done badly, it produces a frontend that works perfectly against a backend that doesn't exist. This guide covers the workflow that keeps it on the right side of that line.
Step 1: Agree on the contract first
A mock is only as useful as its agreement with the real API. Before anyone writes a mock response, the frontend and backend developers should agree on, and write down:
- The method and path —
GET /v1/orders?status=shipped&limit=20 - Required headers, especially auth
- The success response body, with real field names and types
- The error responses and their status codes
- Pagination: cursor or offset? Where does the "next" pointer live?
The best place to write this down is an OpenAPI document, because it can drive the mock, the documentation, and later the contract tests from a single file. Even a shared collection of example requests and responses is far better than a conversation nobody recorded.
Step 2: Make responses realistic, not minimal
The most common mock is too perfect:
[
{ "id": 1, "name": "Test", "total": 10 }
]
Every name is short, every list has one item, nothing is null, and the UI built against it breaks on day one of real data. Aim instead for responses that exercise the UI:
{
"data": [
{
"id": "ord_01J9Z3K8",
"customer": { "name": "Anneliese Vanderberg-Oyelaran", "email": null },
"total": { "amount": 1249900, "currency": "INR" },
"status": "partially_refunded",
"created_at": "2026-09-28T23:59:41Z",
"items_count": 37
}
],
"next_cursor": "eyJpZCI6Im9yZF8wMUo5WjNLOCJ9"
}
That single record checks long names that must wrap or truncate, a null the UI must handle, money stored as integer minor units with an explicit currency, a less common status value, a timestamp near midnight UTC to expose time-zone bugs, and a cursor so pagination gets built from the start.
Include at least one empty-list response too. "No orders yet" is a screen that needs designing, and it's easy to forget when the mock always returns data.
Step 3: Mock the failures, not just the happy path
A frontend that has only ever seen 200 OK has never been tested. Make it easy to trigger each error the contract defines:
| Scenario | Status | What the UI should do |
|---|---|---|
| Token expired | 401 | Refresh the token or redirect to login |
| Not allowed | 403 | Show an access message, and don't loop to login |
| Order doesn't exist | 404 | Show a not-found state |
| Validation failed | 422 | Highlight the specific invalid fields |
| Rate limited | 429 + Retry-After | Back off and retry, without spamming |
| Server down | 503 | Show a retry option, and keep the user's input |
A simple convention helps: let the mock return a specific error when a request includes a header such as X-Mock-Scenario: rate-limited, or when a known ID like ord_404 is requested. See our HTTP status code guide for which code fits which situation.
Step 4: Add latency
A local mock responds in about a millisecond. The real API won't. Without artificial delay you'll never see your loading states, and you'll miss race conditions, such as a user clicking "next page" twice before the first response lands. Adding 300–1500 ms of random delay to mock responses makes those problems visible during development instead of in production.
Step 5: Keep the mock honest
The real danger of mocking is drift. The backend renames items_count to item_count, nobody updates the mock, and the frontend keeps passing every test right up until integration day. Ways to prevent it:
- Generate mocks from the contract rather than writing them by hand, so changing the OpenAPI document changes the mock.
- Run contract tests against the real backend in CI as soon as it exists, validating its responses against the same schema the mock uses.
- Record real responses once the endpoint works, and replace hand-written mock bodies with them — scrubbing any personal data first.
- Delete the mock for an endpoint once the real one is stable in shared environments. A mock nobody maintains is worse than none.
Where to run the mock
- A local mock server on a developer's machine — fast, private, and works offline. Best for day-to-day development.
- In-app interception, using a service worker or request-interception library inside the frontend, is great for component tests and demos. The trade-off is that it only mocks the app that contains it.
- A shared hosted mock — useful when several teams or a mobile app need the same stand-in. It costs more coordination to keep current.
Whichever you use, point the frontend at it through configuration, such as an environment variable holding the API base URL, never through code changes. Switching between the mock and the real backend should be a one-line config change, and it should be easy to see which one you're talking to.
A checklist before you start
- The contract is written down and both sides have agreed to it.
- Mock data includes long values, nulls, empty lists, and edge-case statuses.
- Every documented error can be triggered on demand.
- Responses have realistic latency.
- There's a plan to validate the real backend against the same contract.