Using Storybook And Mock Service Worker For Mocked Api Responses

Develop API-driven components in isolation without waiting for backend APIs. A complete guide to integrating Mock Service Worker with Storybook for faster, more reliable frontend development.

Modern web development increasingly relies on API-driven components that fetch and display data from backend services. However, backend development often lags behind frontend implementation, creating dependencies that slow down the development process. Storybook combined with Mock Service Worker (MSW) provides a powerful solution for developing and testing components in isolation without requiring a live API.

Storybook is an open-source tool for building, testing, and documenting UI components in isolation. It provides a development environment where developers can render components with different props, states, and configurations without running the full application. Our web development services team regularly uses Storybook to accelerate component development workflows.

Mock Service Worker is a declarative API mocking library that uses the browser's Service Worker API to intercept network requests. When a component makes an HTTP request, MSW catches it before it leaves the browser and returns a configured response instead.

Why Use Storybook With MSW

Component Isolation

Develop components without the complexity of the full application context

API Simulation

Mock REST and GraphQL responses without a real backend

State Testing

Test loading, error, and success states with controlled responses

Living Documentation

Generate component catalogs that help teams understand and reuse components

Setting Up MSW With Storybook

Integrating MSW with Storybook involves installing dependencies, generating service worker files, and configuring Storybook to initialize MSW and apply mock handlers to stories. The process is straightforward and well-supported by the official MSW Storybook addon.

Installation

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

The msw-storybook-addon provides convenient integration points that simplify the setup process. After running the initialization command, a mockServiceWorker.js file is created in the public directory and must be served by Storybook's static file configuration.

Configuration

In your .storybook/preview.ts, add the following configuration:

import { initialize, mswLoader } from 'msw-storybook-addon';

initialize({
 onUnhandledRequest: 'bypass',
});

export const loaders = [mswLoader];

The onUnhandledRequest: 'bypass' option ensures that requests without matching handlers pass through to the real network rather than causing errors. This approach aligns with the MSW declarative mocking philosophy, which emphasizes transparent interception at the network level. For teams building comprehensive API integration solutions, this setup enables parallel frontend and backend development.

.storybook/preview.ts - MSW Configuration
1import { initialize, mswLoader } from 'msw-storybook-addon';2import type { Preview } from '@storybook/react';3 4// Initialize MSW with bypass for unhandled requests5initialize({6 onUnhandledRequest: 'bypass',7});8 9const preview: Preview = {10 loaders: [mswLoader],11 // ... other preview configuration12};13 14export default preview;

Creating Mock Handlers For REST APIs

REST API mocking with MSW involves defining handlers that match request patterns and return appropriate responses. Each handler specifies the HTTP method, URL pattern, and response function. This declarative approach, as demonstrated in the Thinkmill tutorial on Storybook and mock APIs, provides precise control over mock behavior.

REST Handler Structure

Handlers can extract URL parameters, query strings, and request bodies when constructing responses. This flexibility enables sophisticated mock behavior that mirrors actual API functionality. When working with RESTful API design, establishing consistent patterns for request handling improves maintainability across your codebase.

import { http, HttpResponse } from 'msw';

export const handlers = [
 http.get('/api/users/:userId', ({ params }) => {
 const { userId } = params;
 return HttpResponse.json({
 id: userId,
 name: 'Jane Developer',
 email: '[email protected]',
 role: 'senior-engineer'
 });
 }),

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

This pattern allows you to simulate various API scenarios including error states, empty results, and delayed responses for comprehensive component testing.

Creating Mock Handlers For GraphQL APIs

GraphQL mocking follows a similar pattern to REST but operates on GraphQL operation names rather than URL patterns. This approach aligns with GraphQL's single-endpoint architecture and allows precise targeting of specific queries and mutations, as demonstrated in the Storybook documentation.

GraphQL Handler Syntax

import { graphql, HttpResponse } from 'msw';

export const handlers = [
 graphql.query('GetUserProfile', ({ variables }) => {
 const { userId } = variables;
 return HttpResponse.json({
 data: {
 user: {
 id: userId,
 username: 'frontenddev',
 bio: 'Building digital experiences',
 avatarUrl: 'https://example.com/avatar.png',
 posts: {
 nodes: [
 { id: '1', title: 'Getting Started with MSW' },
 { id: '2', title: 'Storybook Best Practices' }
 ]
 }
 }
 }
 });
 }),

 graphql.mutation('CreatePost', ({ variables }) => {
 const { title, content } = variables;
 return HttpResponse.json({
 data: {
 createPost: {
 id: 'new-post-id',
 title,
 content,
 createdAt: new Date().toISOString()
 }
 }
 });
 })
];

The handler response returns data structured according to the GraphQL schema, matching exactly what the component expects from the real API. For teams implementing GraphQL solutions, MSW provides consistent mock behavior across development and testing environments.

ArticleList.stories.tsx - Applying GraphQL Handlers
1import type { Meta, StoryObj } from '@storybook/react';2import { ArticleList } from './ArticleList';3import { handlers } from '../../mocks/handlers';4 5const meta: Meta<typeof ArticleList> = {6 component: ArticleList,7};8 9export default meta;10type Story = StoryObj<typeof ArticleList>;11 12export const Default: Story = {13 render: () => <ArticleList />,14 parameters: {15 msw: { handlers },16 },17};18 19export const Loading: Story = {20 render: () => <ArticleList />,21 parameters: {22 msw: {23 handlers: [24 graphql.query('GetNewsArticles', () => {25 return new HttpResponse(null, { status: 200 });26 })27 ],28 },29 },30};31 32export const ErrorState: Story = {33 render: () => <ArticleList />,34 parameters: {35 msw: {36 handlers: [37 graphql.query('GetNewsArticles', () => {38 return HttpResponse.json(39 { errors: [{ message: 'Failed to load articles' }] },40 { status: 200 }41 );42 })43 ],44 },45 },46};

Best Practices For Mock API Development

Maintaining API Contract Alignment

Mocks should accurately reflect the current state of API contracts. As backend implementations evolve, mocks must be updated to match new response structures, field names, and behaviors. Sharing mock handler files between frontend and backend teams creates a collaborative contract that both sides can reference, ensuring consistency across the development workflow.

Performance Optimization

While mocking eliminates network latency, handlers that perform complex operations or return large data sets can still impact performance. Complex response builders should be optimized, and mock data should be appropriately sized for the testing scenarios they support. Consider using lazy-loaded mock data for large datasets.

Organizing Mock Data Files

Separate mock data files from handler definitions improve maintainability:

// mocks/data/users.ts
export const mockUser = {
 id: '123',
 name: 'Jane Developer',
 email: '[email protected]',
 avatarUrl: 'https://example.com/avatar.png',
};

// mocks/handlers/users.ts
import { http, HttpResponse } from 'msw';
import { mockUser } from '../data/users';

export const userHandlers = [
 http.get('/api/users/:userId', () => {
 return HttpResponse.json(mockUser);
 })
];

This separation of concerns makes it easier to maintain mock data and update responses as APIs evolve. Following these best practices for component testing ensures your mock API infrastructure scales sustainably with your project.

Frequently Asked Questions

Ready to Accelerate Your Component Development?

Our team specializes in modern frontend development workflows that maximize productivity and code quality. Learn how we can help you implement Storybook and MSW in your projects.

Sources

  1. Storybook: Mocking Network Requests - Official documentation for MSW addon setup and configuration
  2. Mock Service Worker (MSW) Official Site - API mocking library documentation and best practices
  3. Thinkmill: Storybook and Mock APIs - Complete tutorial with code examples for React and GraphQL setup