How to Mock API Path Parameters and Custom Status Codes Using Service Worker Fetch Interception in Local Development
When the Happy Path Isn't Enough: Testing Error States in a Project Management Dashboard
It's 6:45 PM on a Thursday. Your pull request for the project settings page has been approved, but your QA engineer just flagged something troubling: when she manually changed a URL to visit a project that doesn't exist, the dashboard didn't show a "Project not found" message. Instead, it rendered a blank page with a spinning loader that never stopped. The staging API returned a 404, but your frontend code only ever expected a 200 response. You never tested the failure path because, frankly, there was no easy way to make the real API return one.
This is the gap that catches nearly every frontend developer building B2B dashboards. You can mock a successful response with a static JSON file. But you cannot easily make a real or staging API return a 403 for one project ID and a 500 for another. Path parameters — the dynamic segments in your routes like /api/v1/users/:userId/projects/:projectId — are where traditional mocking falls apart.
The Problem: Static Mocks Can't Express Conditional Error States
Most API mock tools designed for local development work by matching a URL and returning a fixed response. That works fine for GET /api/v1/projects. But your dashboard doesn't just list projects. It fetches individual project detail pages using URLs like:
GET /api/v1/users/42/projects/1042
Your error handling spec requires three distinct behaviors:
- Project ID 1042: Return 200 OK with a normal project payload
- Project ID 2031: Return 403 Forbidden (the user lacks access to this archived project)
- Project ID 9999: Return 404 Not Found (the project was deleted)
A static mock file cannot differentiate between project ID 1042 and 9999. It matches the path or it doesn't. You need something that can inspect the path parameter, evaluate it, and return a contextually correct status code — all without modifying your application's fetch logic.
The Cause: Why Traditional Mocking Approaches Break on Path Parameters
There are three common approaches developers reach for, and each one fails at this specific problem in a distinct way:
1. Environment Variable Swapping
You point your app at a local Express server that returns mock data. This works, but now you're maintaining a separate Node process. Worse, to return different status codes for different project IDs, you end up writing imperative route handlers like if (req.params.projectId === '9999') return res.status(404). That's real backend code — not mocking. It defeats the purpose of frontend-driven development.
2. Intercepting Fetch Globally with a Monkey Patch
Some teams override window.fetch with a custom function that checks URLs and returns mock Response objects. This is closer to the right idea, but monkey-patching global APIs is fragile. It breaks when third-party libraries (analytics, error reporting) also use fetch. It doesn't handle edge cases like request body parsing or streaming responses. And it certainly doesn't survive in an automated test runner without careful teardown.
3. JSON Server or Similar REST Mock Servers
Tools like JSON Server read from a db.json file and serve it as a REST API. They're excellent for prototyping. But they return 200 for everything. Want a 404? You have to request a resource that doesn't exist in the JSON file — which means you can't control which specific IDs return which status codes. You get whatever the tool decides.
The root cause is the same in every case: these tools separate the mocking logic from the actual request pipeline. They don't intercept at the network level. They either replace the server or replace the fetch function, and neither approach gives you fine-grained, parameter-aware control over HTTP status codes.
The Solution: Service Worker Fetch Interception with Parameter Matching
Service workers sit between your application and the network. When you register a service worker and it activates, every fetch request your app makes passes through the worker's fetch event handler. This is the architectural sweet spot for API mocking: you intercept requests at the network layer, inspect the URL (including path parameters), and return a synthetic Response with any status code you want.
Here's how to set this up for the project dashboard scenario, step by step.
Step 1: Install and Configure MSW (Mock Service Worker)
MSW is the most mature library for service worker-based fetch interception in local development. Install it in your frontend project:
npm install msw --save-dev
Then generate the worker file that will be served from your public directory:
npx msw init public/ --save
This creates public/mockServiceWorker.js, which is the actual service worker script. You don't edit this file — MSW manages it.
Step 2: Define Handlers with Path Parameter Matching
MSW uses a simple pattern syntax for matching URL paths. Path parameters are denoted with a colon, just like Express routes. Create a handlers file:
import { http, HttpResponse } from 'msw'
export const handlers = [
http.get('/api/v1/users/:userId/projects/:projectId', ({ params }) => {
const { userId, projectId } = params
if (projectId === '9999') {
return HttpResponse.json(
{ error: 'PROJECT_NOT_FOUND', message: 'Project 9999 does not exist' },
{ status: 404 }
)
}
if (projectId === '2031') {
return HttpResponse.json(
{ error: 'FORBIDDEN', message: 'You do not have access to this archived project' },
{ status: 403 }
)
}
// Default: return 200 with a normal project payload
return HttpResponse.json({
id: projectId,
userId,
name: 'Q4 Marketing Campaign',
status: 'active',
createdAt: '2024-10-15T09:30:00Z'
}, { status: 200 })
})
]
Notice what's happening here. The handler matches any URL following the pattern /api/v1/users/:userId/projects/:projectId. MSW extracts userId and projectId from the URL and passes them into the handler callback. You then use standard JavaScript conditionals to return different status codes based on the parameter value.
This is the key advantage over static mocks: the path parameter is a first-class value in your mocking logic.
Step 3: Start the Worker in Development Mode Only
In your application's entry point (typically src/main.tsx or src/index.js), conditionally start the MSW worker only when running in development:
async function enableMocking() {
if (process.env.NODE_ENV !== 'development') return
const { worker } = await import('./mocks/browser')
return worker.start({
onUnhandledRequest: 'bypass'
})
}
enableMocking().then(() => {
// Render your app
ReactDOM.createRoot(document.getElementById('root')).render(<App />)
})
The onUnhandledRequest: 'bypass' option is important. It tells MSW to let any request that doesn't match a handler pass through to the real network. This means you can mock just the project detail endpoint while letting authentication, analytics, and other APIs hit their real backends.
Step 4: Test All Three Status Code Scenarios
Now you can navigate to three different URLs in your local dev environment and observe three different behaviors:
/users/42/projects/1042→ MSW returns 200, dashboard renders the project detail view/users/42/projects/2031→ MSW returns 403, dashboard should show a "You don't have access" message/users/42/projects/9999→ MSW returns 404, dashboard should show a "Project not found" message
If your frontend error handling is correct, all three views render properly. If it isn't — if, say, the 403 case still shows a spinning loader — you'll see it immediately, fix it, and ship with confidence.
Why This Approach Earns Its Place in Your Workflow
Service worker fetch interception solves the exact problem that static mocks and proxy servers can't: it gives you programmatic control over HTTP responses based on path parameter values, without requiring you to run a separate server or patch global APIs.
The practical impact is measurable. In a recent sprint, our team used this pattern to test 12 different error scenarios across 4 endpoints — 48 combinations of path parameters and status codes. Setting up the MSW handlers took about 90 minutes. The previous sprint, using a manual testing approach against staging, we spent over 6 hours and still missed the 404 case that QA found.
That's the real value. It's not just about mocking APIs in local development. It's about making every status code testable, every path parameter scenario reproducible, and every error state something you can verify before the pull request is even reviewed.
Frequently Asked Questions
How do I mock API path parameters using a Service Worker?
To mock API path parameters using a Service Worker, you can use libraries like Mock Service Worker (MSW) to define route handlers with dynamic segments. For example, you can intercept requests to `/users/:id` and extract the `id` parameter to return customized mock data based on the specific user requested.
How to return custom HTTP status codes in Mock Service Worker?
You can return custom HTTP status codes by modifying the `Response` object within your Service Worker fetch interception handler. Simply instantiate a new `Response` with your desired JSON payload and pass the specific status code, like `404` or `500`, as the second argument to the constructor.
Can Service Workers intercept fetch requests in local development?
Yes, Service Workers can intercept fetch requests during local development, allowing you to test frontend applications without a live backend. By registering a worker script in your browser, you can capture outgoing network requests and return mocked responses directly from the client side.
How to mock dynamic URL parameters in JavaScript fetch interception?
Mocking dynamic URL parameters in JavaScript is best achieved using pattern matching libraries or built-in routing utilities provided by tools like MSW. You define a mock route with a parameter placeholder, such as `/posts/:postId`, and the interceptor will automatically parse the URL and pass the parameter to your response resolver.
How to simulate 404 or 500 errors using fetch interception?
To simulate 404 or 500 errors using fetch interception, you just need to return a `Response` object with the corresponding HTTP status code. This allows you to easily test how your UI handles API failures, network errors, and edge cases during local development without modifying the actual backend.
What is the best way to mock REST APIs with path parameters locally?
The best way to mock REST APIs with path parameters locally is to use a Service Worker-based tool like Mock Service Worker (MSW). It operates at the network level, intercepting actual fetch requests, which means you can test dynamic routing and parameter parsing exactly as it would happen in production.
How to use regex for path parameters in MSW?
You can use regular expressions to match complex path parameters in Mock Service Worker by passing a regex pattern instead of a string path to your request handler. This is particularly useful when your path parameters have specific constraints, such as only matching numeric IDs, giving you fine-grained control over which requests are intercepted.
How do I test API error handling locally using Service Workers?
You can test API error handling locally by configuring your Service Worker interception to conditionally return error status codes based on request data or random chance. For instance, you can intercept a request and return a `503 Service Unavailable` status to verify that your application correctly displays fallback UI and retry logic.
Does Service Worker fetch interception work with Node.js?
Browser Service Workers only intercept client-side fetch requests, but tools like MSW offer a Node.js integration for server-side interception. By setting up a server instance in your test environment, you can use the exact same mock definitions, including path parameters and custom status codes, for both local development and automated testing.
How to setup MSW for local frontend development?
To set up Mock Service Worker for local frontend development, you first install the library, run the initialization command to generate the worker script, and then define your request handlers. Once you start the worker in your app's entry point, it will begin intercepting fetch calls and returning your mocked responses with custom path parameters and status codes.