Intercept Fetch API: Mock JSON with Dynamic Placeholders
Quick Win: Intercept Fetch in 10 Lines
Here's the fastest way to hijack a fetch request and return mock data: register a service worker, listen for fetch events, and call respondWith() with a synthetic Response. That's it. No libraries, no backend, no proxy server. The browser does the heavy lifting for you.
If you're building or testing against an API that doesn't exist yet — or one that's rate-limited, flaky, or behind auth — service workers give you a local interception layer that returns fake JSON with dynamic placeholders in milliseconds. A typical mocked response resolves in under 2ms, compared to 150–400ms for a real network round trip. That's roughly a 75x speedup for your dev loop.
Let's break down each piece of the puzzle.
1. Register the Service Worker Correctly
Your service worker file must be served from the same origin as your app, and it must be registered before any fetch calls fire. Place the registration script in your main entry point — typically index.html or your app's bootstrap file.
```javascript if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('/sw.js') .then(reg => console.log('SW registered:', reg.scope)) .catch(err => console.error('SW failed:', err)); }); } ```
Scope Matters
The service worker's scope determines which requests it can intercept. A worker at /sw.js controls the entire origin. A worker at /api/sw.js only controls /api/*. For API mocking, root-level registration is usually what you want.
One gotcha: the service worker only activates on the second page load. During development, use self.skipWaiting() inside the worker and clients.claim() to take control immediately.
2. Listen for Fetch Events and Match URL Patterns
Inside sw.js, add a fetch event listener. This fires for every network request the page makes — images, scripts, stylesheets, XHR, and fetch calls alike. You need to filter aggressively.
```javascript self.addEventListener('fetch', (event) => { const url = new URL(event.request.url);
// Only intercept API calls if (url.pathname.startsWith('/api/users')) { event.respondWith(handleUserRequest(event.request)); return; }
// Let everything else pass through // (no respondWith = default browser behavior) }); ```
Pattern Matching Strategies
You can match on pathname, query parameters, request method, or headers. For a mock tools workflow, a simple startsWith() or regex check is usually enough. If you're mocking a REST API with dynamic IDs like /api/users/123, use a regex:
```javascript const userPattern = /\/api\/users\/(\d+)$/; const match = url.pathname.match(userPattern); if (match) { const userId = match[1]; // "123" event.respondWith(mockUserResponse(userId)); } ```
3. Return Fake JSON with respondWith()
The respondWith() method expects a Promise that resolves to a Response object. You build this Response manually — status code, headers, and body all under your control.
```javascript function mockUserResponse(userId) { const fakeData = { id: userId, name: 'Jane Developer', email: 'jane@example.com', role: 'admin' };
return new Response(JSON.stringify(fakeData), { status: 200, headers: { 'Content-Type': 'application/json', 'X-Mocked-By': 'service-worker' } }); } ```
The key detail: JSON.stringify() your object before passing it to Response. The body must be a string, Blob, or ArrayBuffer — not a raw JavaScript object.
4. Inject Dynamic Placeholders Using Template Replacement
Static mock data gets stale fast. The real power of service worker interception is generating dynamic placeholders — fake data that changes based on the request URL, query params, or even the time of day.
Here's a template-based approach:
```javascript const templates = { user: { id: '{{userId}}', name: '{{fullName}}', email: '{{email}}', avatar: 'https://i.pravatar.cc/150?u={{userId}}', joinedAt: '{{timestamp}}' } };
function fillTemplate(template, params) { const placeholders = { userId: params.id || Math.floor(Math.random() * 10000), fullName: ['Alex Chen', 'Sam Rivera', 'Jordan Lee', 'Casey Morgan'][Math.floor(Math.random() * 4)], email: `user${params.id}@mock.dev`, timestamp: new Date().toISOString() };
return JSON.parse( JSON.stringify(template).replace(/\{\{(\w+)\}\}/g, (_, key) => placeholders[key] || '') ); } ```
Now every request to /api/users/42 returns a unique JSON object with id: 42, a random name, a matching email, and a live timestamp. Your frontend gets realistic, varied data without touching a real server.
Placeholder Ideas That Add Realism
- {{counter}} — increment a global counter for unique IDs across responses - {{latency}} — simulate network delay with setTimeout before resolving - {{errorRate}} — randomly return 500 errors to test error handling (e.g., 10% failure rate) - {{pagination}} — parse ?page=2&limit=20 from the URL and slice mock arrays accordingly
5. Simulate Realistic Network Conditions
Real APIs aren't instant. If your mock returns in 1ms but production returns in 300ms, your loading states never get tested. Wrap your response in a delay:
```javascript function delayedResponse(data, minMs = 100, maxMs = 400) { const delay = Math.random() * (maxMs - minMs) + minMs; return new Promise(resolve => { setTimeout(() => resolve(new Response(JSON.stringify(data), { headers: { 'Content-Type': 'application/json' } })), delay); }); } ```
Use this inside respondWith() and your UI will behave like it's talking to a real backend. You can even simulate failures:
```javascript function maybeFail(response, probability = 0.1) { if (Math.random() < probability) { return new Response(JSON.stringify({ error: 'Internal Server Error' }), { status: 500, headers: { 'Content-Type': 'application/json' } }); } return response; } ```
A 10% failure rate during development catches edge cases that happy-path testing misses entirely.
6. Handle POST, PUT, and DELETE Requests
Interception isn't just for GET requests. Service workers see every HTTP method. For POST and PUT, you can read the request body and return a mock response that reflects the submitted data:
```javascript async function handleCreateUser(request) { const body = await request.json(); // parse the incoming POST body
const mockResponse = { id: Math.floor(Math.random() * 10000), ...body, // echo back submitted fields createdAt: new Date().toISOString(), status: 'active' };
return new Response(JSON.stringify(mockResponse), { status: 201, headers: { 'Content-Type': 'application/json' } }); } ```
For DELETE requests, return a 204 No Content with an empty body. For PUT, echo the updated resource back with a new updatedAt timestamp.
7. Organize Mocks with a Route Map
As your mock surface grows, a giant if/else chain becomes unmaintainable. Build a route table instead:
```javascript const routes = [ { method: 'GET', pattern: /\/api\/users$/, handler: listUsers }, { method: 'GET', pattern: /\/api\/users\/(\d+)$/, handler: getUser }, { method: 'POST', pattern: /\/api\/users$/, handler: createUser }, { method: 'DELETE', pattern: /\/api\/users\/(\d+)$/, handler: deleteUser } ];
self.addEventListener('fetch', (event) => { const url = new URL(event.request.url); const route = routes.find(r => r.method === event.request.method && r.pattern.test(url.pathname) );
if (route) { const matches = url.pathname.match(route.pattern); event.respondWith(route.handler(event.request, matches)); } }); ```
Each handler is a small, testable function. Adding a new mock endpoint means adding one entry to the array. This structure mirrors how dedicated API mock tools organize their route definitions.
8. Debug and Iterate Without Tears
Service workers are notoriously tricky to debug. Three practices that save hours:
1. Log every interception. Add a console.log inside your fetch handler showing the method, URL, and whether you mocked or passed through. Check it in DevTools → Application → Service Workers → console.
2. Use the "Update on reload" checkbox. In Chrome DevTools, under Application → Service Workers, check this box. It forces the worker to re-register on every page load, so your code changes take effect immediately.
3. Add a custom header to mock responses. The X-Mocked-By header from earlier lets you verify in the Network tab that a response came from your service worker, not the real server. Filter by this header to see only mocked traffic.
9. Clean Up When You're Done
Mock service workers shouldn't ship to production. To remove the interceptor:
```javascript navigator.serviceWorker.getRegistrations().then(regs => { regs.forEach(reg => reg.unregister()); console.log('All service workers unregistered'); }); ```
Run this in the console when you're done mocking, or gate registration behind an environment check:
```javascript if (process.env.NODE_ENV === 'development' && 'serviceWorker' in navigator) { navigator.serviceWorker.register('/mock-sw.js'); } ```
This ensures the interceptor only loads during local development, never in a production build.
When to Reach for Service Worker Mocking vs. Dedicated Tools
Service worker interception shines for quick, local, browser-based mocking — no dependencies, no server, full control over dynamic placeholders. It's ideal for frontend developers who need to unblock themselves when the backend isn't ready.
For larger teams or more complex scenarios — shared mock definitions, collaborative editing, persistent mock servers, OpenAPI schema generation — dedicated API mock tools offer features that raw service workers don't. But for a single developer who needs fake JSON with dynamic placeholders in under five minutes, the service worker approach is hard to beat.
The entire setup — registration, fetch listener, template engine, route map — fits in roughly 120 lines of JavaScript. No build step. No npm install. Just a file and a script tag.
Frequently Asked Questions
How do I intercept fetch API requests using a service worker?
You can intercept fetch API requests by adding a `fetch` event listener inside your service worker file. Within this event listener, call `event.respondWith()` to take control of the network request and provide a custom response instead of letting it hit the actual server.
How do I return fake JSON from a service worker?
To return fake JSON, create a new `Response` object inside your `fetch` event listener and pass your JSON string as the first argument. Make sure to set the appropriate headers, such as `{ 'Content-Type': 'application/json' }`, so the browser parses the mock data correctly.
Can I use dynamic placeholders in mocked API responses?
Yes, you can use dynamic placeholders by templating your fake JSON response and replacing tokens with generated data before returning the response. For example, you can replace a `{{id}}` placeholder with a random number or a timestamp using standard JavaScript string replacement methods.
How to mock REST API calls using a service worker?
Mocking REST API calls involves inspecting the `event.request.url` and `event.request.method` within your service worker's fetch event. You can then route specific endpoints to return tailored fake JSON responses based on the HTTP method and URL pattern.
What is Mock Service Worker (MSW) and how does it help with API mocking?
Mock Service Worker (MSW) is a library that abstracts the complexity of writing raw service worker code for intercepting requests. It allows you to define mock responses and dynamic placeholders declaratively, making it easier to test API calls without managing the service worker lifecycle manually.
How do I modify a fetch response before returning it in a service worker?
If you need to modify an existing response, first fetch the original request using `fetch(event.request)` and then clone the response to alter its body. You can read the original JSON, inject your dynamic placeholders, and wrap the modified payload in a new `Response` object to pass into `event.respondWith()`.
Can service workers intercept cross-origin API requests?
Yes, service workers can intercept both same-origin and cross-origin fetch requests as long as the request is initiated from a page controlled by the service worker. This allows you to mock third-party API responses and return fake JSON data dynamically during development or testing.
How to generate random mock data dynamically in a service worker?
You can generate random mock data by integrating a fake data generation library like Faker.js directly into your service worker script. When a request is intercepted, use the library to create realistic dynamic values for your placeholders before constructing the final JSON response.
Why are my service worker mock responses not showing up?
This usually happens because the service worker has not been properly registered or the page hasn't been refreshed to activate the new scope. Always ensure you unregister old service workers, clear your cache, and hard reload the browser to see your updated fake JSON responses.
How do I bypass the service worker for specific API requests?
To bypass interception for specific endpoints, simply avoid calling `event.respondWith()` for those requests inside your fetch event listener. If `event.respondWith()` is not called, the browser will naturally fall back to the actual network request, allowing you to mock only the APIs you need.