MSW Has a Free API Mocking Framework — Stop Using Custom Mock Servers

You’re probably mocking APIs wrong. Custom mock servers, intercepting fetch globally, hardcoding responses — all fragile, all pain.

MSW (Mock Service Worker) fixes this at the network level.

What is MSW?

MSW intercepts HTTP requests at the network level using Service Workers (browser) or custom interceptors (Node.js). Your application code doesn’t know it’s being mocked. That’s the point.

Why MSW Changes Everything

1. Network-Level Interception

import { http, HttpResponse } from 'msw';

export const handlers = [
  http.get('/api/users', () => {
    return HttpResponse.json([
      { id: 1, name: 'Alice' },
      { id: 2, name: 'Bob' },
    ]);
  }),

  http.post('/api/users', async ({ request }) => {
    const user = await request.json();
    return HttpResponse.json({ id: 3, ...user }, { status: 201 });
  }),
];

Your fetch(), axios, ky — all intercepted. No code changes needed.

2. Same Mocks for Browser AND Node.js

// Browser (Storybook, dev server)
import { setupWorker } from 'msw/browser';
const worker = setupWorker(...handlers);
worker.start();

// Node.js (tests, SSR)
import { setupServer } from 'msw/node';
const server = setupServer(...handlers);
server.listen();

Write mocks once. Use everywhere.

3. Request Handlers That Feel Natural

import { http, HttpResponse } from 'msw';

const handlers = [
  // Path parameters
  http.get('/api/users/:id', ({ params }) => {
    return HttpResponse.json({ id: params.id, name: 'Alice' });
  }),

  // Query parameters
  http.get('/api/search', ({ request }) => {
    const url = new URL(request.url);
    const query = url.searchParams.get('q');
    return HttpResponse.json({ results: [`Result for ${query}`] });
  }),

  // Error responses
  http.get('/api/protected', () => {
    return HttpResponse.json(
      { error: 'Unauthorized' },
      { status: 401 }
    );
  }),

  // Network errors
  http.get('/api/unstable', () => {
    return HttpResponse.error();
  }),
];

4. Perfect for Testing

import { setupServer } from 'msw/node';
import { http, HttpResponse } from 'msw';

const server = setupServer(...handlers);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

test('shows user profile', async () => {
  // Override for this specific test
  server.use(
    http.get('/api/users/1', () => {
      return HttpResponse.json({ id: 1, name: 'Test User', role: 'admin' });
    })
  );

  render(<UserProfile userId="1" />);
  expect(await screen.findByText('Test User')).toBeInTheDocument();
});

test('handles API errors gracefully', async () => {
  server.use(
    http.get('/api/users/1', () => {
      return HttpResponse.json({ error: 'Not found' }, { status: 404 });
    })
  );

  render(<UserProfile userId="1" />);
  expect(await screen.findByText('User not found')).toBeInTheDocument();
});

5. WebSocket Mocking

import { ws } from 'msw';

const chat = ws.link('wss://api.example.com/chat');

export const handlers = [
  chat.addEventListener('connection', ({ client }) => {
    client.send(JSON.stringify({ type: 'connected', timestamp: Date.now() }));

    client.addEventListener('message', (event) => {
      client.send(JSON.stringify({
        type: 'echo',
        data: event.data
      }));
    });
  }),
];

MSW vs Other Mocking Approaches

MSW Jest mocks Nock JSON Server
Level Network Module HTTP Server
Browser Yes No No Yes
Node.js Yes Yes Yes Yes
Framework agnostic Yes Jest only Node only Yes
WebSocket Yes No No No
No code changes Yes Requires import changes Yes Yes

Getting Started

npm install msw --save-dev
npx msw init public/ --save

Create src/mocks/handlers.ts, define your handlers, and you’re done.

The Bottom Line

MSW is the standard for API mocking in 2026. Network-level interception, cross-environment support, and WebSocket mocking make it the tool your test suite has been missing.

Building data-intensive applications? I create custom web scraping and data extraction tools. Check out my Apify actors or email [email protected].