Every frontend developer has experienced this sequence:
- You start building an interactive web application.
- The backend is not yet ready, so you connect to a popular free mock API like JSONPlaceholder or a local static
db.jsonfile. - You fetch data with a
GETrequest. The cards render nicely on screen. - You build a form to create a new user or submit an order. You send a
POSTrequest. The API responds with a cheerful{ id: 101, status: "created" }. - You trigger your UI's refresh action to see the new item in the list.
Nothing happens. The list is identical to what it was before. Your newly created item has vanished into thin air.
For over a decade, developers have accepted this as "normal" mock API behavior. But in modern web development, where applications rely heavily on query caching, optimistic UI updates, and reactive state stores, static mock APIs are no longer sufficient.
The Illusion of the 200 OK "Echo"
Static mock servers operate on a simple principle: stateless echo responses.
When you send a POST request to a traditional static mock endpoint, the server does not store the data anywhere. It simply inspects the JSON payload you sent, appends a hardcoded fake ID (like id: 101), and sends it back to you:
[Frontend] --- POST /posts { title: "Hello World" } ---> [Static Mock Server]
[Frontend] <-- 201 Created { id: 101, title: "Hello" } -- [Static Mock Server]
(Data discarded immediately)
[Frontend] --- GET /posts/101 -------------------------> [Static Mock Server]
[Frontend] <-- 404 Not Found --------------------------- [Static Mock Server]
This statelessness introduces severe friction in three core areas of modern frontend engineering:
1. TanStack Query / SWR Cache Invalidation Breaks
In modern React and Vue applications, libraries like TanStack Query (React Query) manage server state. A standard mutation pattern looks like this:
// TanStack Query mutation pattern
const mutation = useMutation({
mutationFn: createPost,
onSuccess: () => {
// Invalidate and refetch the posts query
queryClient.invalidateQueries({ queryKey: ['posts'] });
},
});
With a static mock API, when invalidateQueries triggers a background GET request, the server returns the original un-mutated seed list. The UI flickers, and your created item disappears. Developers end up writing artificial client-side hacks just to simulate what a real backend does automatically.
2. Form Edits (PUT / PATCH) Cannot Be Verified
When you update a user’s profile name or email, you need to verify that subsequent navigation steps (such as going to an Account Settings page or viewing an avatar card) reflect the updated values. Static mock APIs return 200 OK on PATCH, but subsequent GET /users/1 calls still return the original seed name.
3. Deletion and Empty States Cannot Be Tested
If your UI has conditional rendering for empty states ("No items found. Click here to add one"), you cannot test this flow by deleting records through the UI.
The Solution: Stateful Session-Based Mock APIs
The solution is not to force frontend developers to build SQLite or Postgres backends for every prototype. The solution is stateful API mocking.
A stateful mock API preserves mutations across requests without requiring developers to maintain a database.
[User Browser A] (Session A)
│
├── POST /posts {"title": "Post from Tab A"}
│ └── Saved to Overlay Table (Session A)
│
└── GET /posts
└── Returns: [Seed Posts + "Post from Tab A"]
How Virtual Overlays Work Under the Hood
Instead of mutating a global database that would corrupt data for other developers, tools like Playground API use a virtual per-session overlay architecture:
- Shared Read-Only Seed Data: Standard resources (users, posts, comments, todos) are loaded from a global, read-only baseline dataset.
-
Session Identification: When a client sends a request, an HTTP-only session cookie (or
X-Playground-Identityheader) identifies the caller's sandbox. -
Overlay Interception: When a
POST,PUT,PATCH, orDELETErequest arrives, the modification is recorded in a temporary session table. -
Dynamic Merge: When a
GETrequest occurs, the engine merges the baseline seed records with the session's overlays (injecting created records, applying patches, and filtering out deleted IDs).
The result? To your frontend application, the API behaves identically to a production database backend.
Concrete Comparison: Testing a Mutation Workflow
Let's test this in code using fetch:
const API_BASE = 'https://playground.nileslabs.com/api/v1';
async function testStatefulBehavior() {
console.log('1. Creating a new post...');
const createRes = await fetch(`${API_BASE}/posts`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: 'Stateful Mocking in 2026',
body: 'Mutations persist across GET requests within your session.',
user_id: 1,
}),
});
const newPost = await createRes.json();
console.log('Created Post ID:', newPost.id); // e.g. ID: 101
console.log('\n2. Fetching the created post by its ID...');
const getSingleRes = await fetch(`${API_BASE}/posts/${newPost.id}`);
console.log('GET Single Status:', getSingleRes.status); // 200 OK!
const fetchedPost = await getSingleRes.json();
console.log('Fetched Post Title:', fetchedPost.title);
console.log('\n3. Fetching the full list to verify list inclusion...');
const getListRes = await fetch(`${API_BASE}/posts?_page=1&_limit=5`);
const list = await getListRes.json();
const exists = list.some(item => item.id === newPost.id);
console.log('Is new post present in GET /posts list?', exists); // true
}
testStatefulBehavior();
When Are Static Mocks Still Useful?
Static mocks still have their place in development workflows:
- Unit Component Testing (Storybook / Jest): For isolated UI render tests where a static JSON fixture is sufficient.
- Static Schema Validation: Verifying TypeScript types against hardcoded sample responses.
However, whenever you are testing user interactions, caching lifecycles, form submissions, or end-to-end flows, a stateful mock API is essential.
Summary Checklist
| Requirement | Static Mock API (JSONPlaceholder) | Stateful Sandbox (Playground API) |
|---|---|---|
GET baseline datasets |
✅ Supported | ✅ Supported |
POST returns created item |
✅ Supported (Echo only) | ✅ Supported (Persisted) |
GET /resource/:id after POST
|
❌ Returns 404 | ✅ Returns created record |
DELETE removes item from list |
❌ Item remains | ✅ Item removed |
| TanStack Query Cache Invalidation | ❌ Fails / Inaccurate | ✅ Works out-of-the-box |
| Session Reset Capability | ❌ N/A | ✅ DELETE /session/reset
|
Conclusion
Frontend development has outgrown stateless dummy endpoints. If your UI handles forms, mutations, deletes, and reactive caching, your mock environment should reflect those real-world operations.
Explore persistent sandbox mutations with Playground API by Niles Labs and test your frontend applications against realistic backend behavior.
This article was originally published by DEV Community and written by Nilesh Kumar.
Read original article on DEV Community