# Mock Service Worker (MSW): network-level API mocking for browser and Node.js

> MSW intercepts requests after they leave your application, so handlers stay the same across development, unit tests and E2E runs. Here is how the browser worker and the Node server differ, how it is set up, and where it stops being the right tool.

**mswjs/msw** — The industry standard for API mocking in JavaScript.

- Repository: https://github.com/mswjs/msw
- Website: https://mswjs.io
- Stars: 18,235 · Forks: 630
- Language: TypeScript
- License: MIT
- Published: 2026-09-21 · Updated: 2026-09-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/mswjs-msw

## The problem MSW solves: mocks that leave your application code alone

Most JavaScript mocking tools replace a function. You stub fetch, you stub axios, you stub the query client, and from that moment the code path your application actually runs in production no longer runs in tests. MSW takes the opposite position. The README describes interception happening "on the network level, which means after they have been performed and 'left' your application." Your request code executes unchanged; something below it answers instead of the real server.

The audience is therefore specific. Teams that already have integration or end-to-end tests and want them to exercise real request construction, serialization and error handling. Teams building a frontend against an API that does not exist yet. Teams that want the same mock definition to serve local development in the browser and automated tests in Node, which the README states is possible because the same request handlers are reused across environments.

It is not a general-purpose test double library. If your problem is "this module returns the wrong value," MSW is the wrong shape of tool.

## How interception works in the browser versus in Node.js

The two environments use different mechanisms, and the split is the most important thing to understand about the library.

In the browser, MSW registers a Service Worker. The Service Worker API exists to intercept requests for caching, and MSW reuses it to answer requests with your mock definitions. The README notes an important consequence: although the Service Worker runs in a separate thread, request handlers execute on the client, so handlers can use TypeScript, third-party libraries and internal application logic. In DevTools the mocked response appears in the Network tab like any other response, which is what the Kent C. Dodds quote in the README highlights.

In Node.js there is no Service Worker. The README states that MSW instead implements a low-level interception algorithm (the mswjs/interceptors package) that consumes the same request handlers. That is why the same handler file can back a browser session and a test run.

The request matching syntax is Express-like: parameters, wildcards and regular expressions, with responses carrying status codes, headers, cookies, delays or fully custom resolvers. The package ships separate entry points for the two worlds, `msw/browser` and `msw/node`, and the package.json exports map sets `./browser` to null under the `node` condition, so importing the browser build from a Node context fails loudly rather than silently.

## Setting up MSW and writing your first handler

The README points to the official documentation and quick start at mswjs.io for the full setup, and gives the usage example below for the browser. The package is published as `msw`, and the repository pins pnpm 9.15.0 as its package manager for contributors.

Then describe network behavior with request handlers and start the worker. This is the README's browser example, using `http` and `HttpResponse` from the core entry point and `setupWorker` from the browser entry point:

```js
import { http, HttpResponse } from 'msw'
import { setupWorker } from 'msw/browser'

const worker = setupWorker(
  http.get('https://github.com/octocat', ({ request, params, cookies }) => {
    return HttpResponse.json(
      { message: 'Mocked response' },
      { status: 202, statusText: 'Mocked status' },
    )
  }),
)

await worker.start()
```

After `worker.start()` resolves, a `GET https://github.com/octocat` from your application returns the mocked body with status 202, and the request shows up in the Network tab. The README does not document the generated Service Worker file or the CLI commands that produce it; the quick start page is where that step lives.

For tests, the same handlers are registered through the Node entry point:

```js
import { setupServer } from 'msw/node'

const server = setupServer()
```

## Scoping interception with server.boundary()

A process-wide interception layer is convenient until two tests disagree about what a URL should return. MSW's answer is `server.boundary()`, shown in the README inside an Express route. The boundary scopes request interception to a particular closure, so handlers registered inside it apply only while that closure runs.

The README's Express example registers `setupServer()` at module scope, then inside a route handler calls `server.boundary((req, res) => { server.use(http.get('https://api.stripe.com/v1/checkout/sessions/:id', ...)) })` before continuing with `handleSession(req, res)`. The README calls this "extremely handy," and the design intent is clear: a global interception layer plus a way to narrow it for one unit of work.

This matters because the alternative pattern, calling `server.use()` at the top of every test and `server.resetHandlers()` at the end, leaks state if a test throws before cleanup. The boundary ties the handler's lifetime to a closure instead. The documentation for `setupServer` and its boundary method is the place to check the exact semantics, including what happens to handlers registered inside a boundary that is still open when the process exits.

## Where MSW is the wrong tool

MSW requires a working interception layer, and that layer has prerequisites. In the browser it needs Service Worker support and a served worker file. In Node it needs the interceptors package to attach to the request machinery the code under test uses. If your HTTP client bypasses that machinery, for example through a native addon or a non-standard transport, interception will not see the request. The README does not enumerate which clients are covered.

There is also a cost to the network-level approach. Because requests genuinely leave the application, failures can be harder to localize: a handler that never matches produces a real network request, not an obvious stub error, unless you configure fallback behavior. The README does not document what happens on an unmatched request, so treat that as something to verify in the documentation rather than assume.

And if your goal is unit-testing a pure function that happens to call an API, MSW is heavier than the problem. You are adding a Service Worker or a Node interceptor to answer a question a simple injected fake would answer faster.

## Alternatives and how the approach differs

The most direct alternative is stubbing the client itself, for example replacing `fetch` or an axios adapter in test setup. That approach is simpler to reason about in a single test and requires no worker or interceptor. The difference in approach is exactly what the README argues against: stubbing the client means the request-building code in your application does not run, so serialization bugs, header mistakes and URL construction errors pass tests that would fail in production.

A second alternative is running a real mock server, for example a local HTTP process that your application points at through a base URL. That gives true network behavior and works in any language, but it requires changing configuration between environments, and the README's stated goal is the opposite: keep the application "unaware of whether something is mocked or not."

MSW's distinguishing claim, per the README, is that it "leaves your box intact, 1-1 as it is in production" while living in a separate box next to it. Whether that trade is worth the setup depends on how much of your risk sits in the request layer.

## Maintenance, licence and upgrade cost

The repository is not archived, and the last push was on 2026-09-21. Recent releases include v2.15.0 on 2026-07-08, v2.14.7 on 2026-07-07 and v2.14.6 on 2026-05-11, so the release cadence over that window is uneven rather than regular.

The licence is MIT, which permits commercial and closed-source use and modification, subject to the usual requirement to retain the copyright notice and permission text. That is a description of the terms, not legal advice; the LICENSE.md file in the repository is the authoritative text.

Upgrade cost is concentrated in the major version line. The package exposes multiple conditional export paths (`./`, `./browser`, `./node`) with separate type declarations for ESM and CJS, and `./browser` is explicitly null under the `node` condition. That export map is the thing most likely to break when a bundler, test runner or module resolution setting changes, and it is also what makes an accidental browser import in Node fail immediately rather than at runtime. The CHANGELOG.md and release.config.json at the repository root are where version-to-version changes are recorded.

## Conclusion

Adopt MSW when your tests or local development need real production request paths exercised end to end, and when you want one handler set shared by the browser worker, the Node server and E2E runs. Do not adopt it if you only need to stub a single function, or if your environment cannot register a Service Worker and you are unwilling to use the Node interception path. Before committing, verify that setupWorker registers in your target browsers, that the Node build of MSW resolves through the ./node export in your bundler or test runner, and read the setupServer boundary documentation if you need interception scoped to one closure rather than the whole process.

## FAQ

### What is MSW mocking?

MSW is a JavaScript library that intercepts HTTP requests at the network level. In the browser it uses the Service Worker API to respond to requests with your mock definitions, and in Node.js it uses a low-level interception algorithm with the same request handlers.

### What is MSW used for?

The README describes reusing one mock definition for unit, integration and E2E testing, plus local development and debugging. Handlers are written with Express-like routing syntax, including parameters, wildcards and regular expressions.

### What are some alternatives to MSW?

The README contrasts MSW with libraries that stub fetch, axios or react-query: those replace the part of your code that performs the request, while MSW intercepts after the request has left your application. Running a real local mock server is another approach, but it requires pointing your application at a different base URL.

### What is MSW for testing?

In tests MSW is set up through the Node entry point with `setupServer` from `msw/node`, using the same handlers as the browser. The README states that it does not stub fetch or axios, so tests know nothing about mocking.

## Sources

- [License: MIT](https://github.com/mswjs/msw/blob/main/LICENSE)
- [mswjs/msw on GitHub](https://github.com/mswjs/msw)
- [Project website](https://mswjs.io)
- [README](https://github.com/mswjs/msw/blob/main/README.md)
- [Releases](https://github.com/mswjs/msw/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/mswjs-msw
