Skip to content

Blog · May 2nd, 2024 · 12 min read

Ben Derham avatar Ben Derham avatar

Ben Derham

Storybook and Mock APIs: A Powerful Prototyping Combo

No back-end? No worries! In this tutorial we’ll be using Storybook and a Mock API to create a mocked prototype so we can get on with keeping our stakeholders excited.

You’re a Front-end Engineer tasked with implementing the UI for a killer new app feature. The design is solid, the data schema is ready to go, and everyone is excited to see the feature in action. There’s just one thing preventing you from fulfilling stakeholder dreams… the time it takes to develop the back-end of the API!

Sound familiar? There‘s a simple and effective way of getting around the API not being in place before UI development is complete… prototyping.

If you’re not familiar with the concept of prototyping in software development, it’s the process of creating an early model of a product or feature to test its design and functionality. Prototyping enables exploration and experimentation, highlights potential issues early, and is an incredible medium for communicating and presenting ideas to stakeholders.

In this tutorial we’ll be using Storybook and Mock Service Worker to create a prototype list of news articles for a React application that interfaces with a GraphQL API.

Storybook is a powerful tool for creating and testing UI components in isolation. It will help us focus on the building and iterating of individual components without having to worry about the larger context of the app.

Mock Service Worker is a library for mocking out a GraphQL API and testing app GraphQL queries and mutations in isolation. It is a powerful tool for enhancing prototypes, and will be especially useful in a scenario like this where we don't have access to a deployed API.

Getting setup

Storybook needs to be installed in a project that is already setup with a framework, so before we dive into building our prototype we’ll want to spin up a new React project. We’ll be working from this Mantine Vite (Minimal) Template throughout this tutorial. So, grab a copy of the template and follow the steps below.

Installing Storybook

First of all we’ll need to add Storybook to our project. Storybook provides a CLI allowing us to get setup with a single line command. From your project’s root directory run the following command in your terminal:

yarn dlx storybook@latest init

If all goes well, you should see a setup wizard that provides you with a short tour of Storybook’s main concepts and features.

Storybook Onboarding screen.

The setup wizard will help you get started by introducing you to the main concepts and features of Storybook.

You’ll notice a couple of new folders in your project, .storybook and src/stories. .storybook is where Storybook’s configuration files are saved. src/stories contains some example component stories, which you can leave for reference when creating stories of your own or if you wish, delete this folder entirely.

Setup Mantine in Storybook

As we’re using the Mantine UI Component Library in our project, we’ll also need to setup Mantine in Storybook before creating our first story.

Install the Storybook add-on:

yarn add --dev storybook-dark-mode

Add storybook-dark-mode add-on to .storybook/main.ts:

import type { StorybookConfig } from '@storybook/react-vite';

import { join, dirname } from 'path';

/**
 * This function is used to resolve the absolute path of a package.
 * It is needed in projects that use Yarn PnP or are set up within a monorepo.
 */
function getAbsolutePath(value: string): any {
  return dirname(require.resolve(join(value, 'package.json')));
}

const config: StorybookConfig = {
  // ...config
  addons: [
    // ...addons
    getAbsolutePath('storybook-dark-mode'),
  ],
};

export default config;

Save the .storybook/preview.ts as .storybook/preview.tsx and replace the content with the following:

// Import styles of packages that you‘ve installed.
// All packages except `@mantine/hooks` require styles imports.
import '@mantine/core/styles.css';

import React, { useEffect } from 'react';
import { addons } from '@storybook/preview-api';
import { DARK_MODE_EVENT_NAME } from 'storybook-dark-mode';
import { MantineProvider, useMantineColorScheme } from '@mantine/core';

const channel = addons.getChannel();

function ColorSchemeWrapper({ children }: { children: React.ReactNode }) {
  const { setColorScheme } = useMantineColorScheme();
  const handleColorScheme = (value: boolean) =>
    setColorScheme(value ? 'dark' : 'light');

  useEffect(() => {
    channel.on(DARK_MODE_EVENT_NAME, handleColorScheme);
    return () => channel.off(DARK_MODE_EVENT_NAME, handleColorScheme);
  }, [channel]);

  return <>{children}</>;
}

export const decorators = [
  (renderStory: any) => (
    <ColorSchemeWrapper>{renderStory()}</ColorSchemeWrapper>
  ),
  (renderStory: any) => <MantineProvider>{renderStory()}</MantineProvider>,
];

Mock data

As we don’t have an API to fetch a list of news articles from as yet, we’ll create a “hard-coded” set of mock articles to represent the data we expect to receive from the API. Create a new file called src/mocks/articles.ts:

export const MOCK_ARTICLE_LIST_DATA = [
  {
    id: 1,
    date: '1st January 2024',
    slug: 'article-one',
    title: "Golf’s Worst Kept Secrets: Why We Really Wear Funny Pants",
    imageUrl:
      'https://images.unsplash.com/photo-1519682271141-57c25ad60410?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=540&ixid=MnwxfDB8MXxyYW5kb218MHwxMjAxNTQ0NXx8fHx8fHwxNzE0NjI4ODEy&ixlib=rb-4.0.3&q=80&w=720',
  },
  {
    id: 2,
    date: '10th February 2024',
    slug: 'article-two',
    title: 'Golf Cart Grand Prix: When Tee Time Turns into a Race',
    imageUrl:
      'https://images.unsplash.com/photo-1602991174407-a015b35a7b00?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=540&ixid=MnwxfDB8MXxyYW5kb218MHwxMjAxNTQ0NXx8fHx8fHwxNzE0NjI4ODUx&ixlib=rb-4.0.3&q=80&w=720',
  },
  {
    id: 3,
    date: '12th March 2024',
    slug: 'article-three',
    title:
      "Swing Like a Pro: How to Look Good Even When You’re Missing Every Shot",
    imageUrl:
      'https://images.unsplash.com/photo-1684144064253-bb3b4c8fc700?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=540&ixid=MnwxfDB8MXxyYW5kb218MHwxMjAxNTQ0NXx8fHx8fHwxNzE0NjI4ODEy&ixlib=rb-4.0.3&q=80&w=720',
  },
  // ...articles
]

Creating our component stories

Now that we have Storybook up and running in our React project along with some mock data, we can get into prototyping our news article components. We’re going to need a couple of UI components for our list of news articles: An ArticleCard to display a news articles overview, and an ArticleList to render our news articles in a grid.

src/components/ArticleCard.tsx

import { AspectRatio, Card, Image, Text } from '@mantine/core';

type Props = {
  slug: string;
  date: string;
  title: string;
  imageUrl: string;
};

export const ArticleCard = ({ slug, date, title, imageUrl }: Props) => {
  return (
    <Card p="md" radius="md" component="a" href={`#/articles/${slug}`}>
      <AspectRatio ratio={720 / 540}>
        <Image src={imageUrl} />
      </AspectRatio>
      <Text c="dimmed" size="xs" tt="uppercase" fw={700} mt="md">
        {date}
      </Text>
      <Text fw="bold" mt={5}>
        {title}
      </Text>
    </Card>
  );
};

src/components/ArticleList.tsx

import { Container, SimpleGrid } from '@mantine/core';

import { ArticleCard } from './ArticleCard';

type Props = {
  articles: {
    id: number;
    slug: string;
    date: string;
    title: string;
    imageUrl: string;
  }[];
};

export const ArticleList = ({ articles }: Props) => {
  return (
    <Container py="xl">
      <SimpleGrid cols={{ base: 1, sm: 2 }}>
        {articles.map((article) => (
          <ArticleCard key={article.id} {...article} />
        ))}
      </SimpleGrid>
    </Container>
  );
};

Alongside our ArticleCard and ArticleList components we now create a “story” file for each one like so:

src/components/ArticleCard.stories.tsx

import type { Meta, StoryObj } from '@storybook/react';

import { ArticleCard } from './ArticleCard';

const meta: Meta<typeof ArticleCard> = {
  component: ArticleCard,
};

export default meta;
type Story = StoryObj<typeof ArticleCard>;

export const Default: Story = {
  args: {
    date: '1st January 2024',
    slug: 'article-one',
    title: "Golf’s Worst Kept Secrets: Why We Really Wear Funny Pants",
    imageUrl:
      'https://images.unsplash.com/photo-1519682271141-57c25ad60410?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=540&ixid=MnwxfDB8MXxyYW5kb218MHwxMjAxNTQ0NXx8fHx8fHwxNzE0NjI4ODEy&ixlib=rb-4.0.3&q=80&w=720',
  },
  render: (args) => <ArticleCard {...args} />,
};

src/components/ArticleList.stories.tsx

import type { Meta, StoryObj } from '@storybook/react';

import { ArticleList } from './ArticleList';
import { MOCK_ARTICLE_LIST_DATA } from '../mocks/articles';

const meta: Meta<typeof ArticleList> = {
  component: ArticleList,
};

export default meta;
type Story = StoryObj<typeof ArticleList>;

export const Default: Story = {
  args: {
    articles: MOCK_ARTICLE_LIST_DATA,
  },
  render: (args) => <ArticleList {...args} />,
};

Now when we start up our Storybook using the yarn storybook command, we’ll be able to view and interact with our components.

ArticleList Storybook

ArticleList Storybook

Mocking the GraphQL API

Having our components available in Storybook provides an excellent way of testing, reviewing and iterating on them without having to integrate them into the application itself. We are however limited in our analysis of the components by the fact that they’re currently displaying hard-coded mock data. There’s no sense of what might happen if this data took a while to load, if the API was unable to return any data, or if the API returned an error. Let’s take a look at alleviating these shortfalls by setting up a Mock GraphQL API using Mock Service Worker and Apollo.

Installing Apollo Client

When interacting with a GraphQL API it can be beneficial to leverage the power of a state management library such as Apollo Client to help simplify the handling of remote and local data.

Install Apollo Client Dependencies

yarn add @apollo/client graphql

This will install the two dependencies required to use Apollo Client in our application: Apollo Client, which includes everything required to run itself; and GraphQL, which provides the logic required to parse GraphQL Queries and Mutations.

Add a Mock Apollo Client Provider

We’ll also need to setup an ApolloClient instance that we can use to interact with the Mock API we’re going to be creating shortly.

Inside our “mocks” folder, create a new file called src/mocks/MockApolloProvider.tsx

import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client';

type MockApolloProviderProps = {
  children: React.ReactElement;
};

const client = new ApolloClient({
  uri: '//fake.gql.server',
  cache: new InMemoryCache(),
  defaultOptions: {
    watchQuery: {
      fetchPolicy: 'no-cache',
      errorPolicy: 'all',
    },
    query: {
      fetchPolicy: 'no-cache',
      errorPolicy: 'all',
    },
  },
});

export const MockApolloProvider = ({ children }: MockApolloProviderProps) => {
  return <ApolloProvider client={client}>{children}</ApolloProvider>;
};

Install Mock Service Worker

Let’s go ahead and install Mock Service Worker into our project’s development dependencies:

yarn add --dev msw

Mock Service Worker is designed to intercept any client-side requests our application makes, and mock out a response. To enable this behaviour in the browser, we’ll need to create and register a service worker in our application. The great thing about the Mock Service Worker library is that we don’t actually have to write any code to create this worker ourselves, the library provides a CLI that generates a worker file into our application’s public directory, and registers it in our package.json:

yarn dlx msw init public/ --save

Mocking an API Request

Let’s say in our final application we’re expecting that a list of news articles will be populated by executing a GraphQL Query named GetNewsArticles. To enable the simulation of this query request and possible responses in Storybook, we’ll need to write a mock request handler, and resolver for the GetNewsArticles query.

Go ahead and create a new file src/mocks/handlers.ts with the following code:

import { graphql, HttpResponse } from 'msw'
import { MOCK_ARTICLE_LIST_DATA } from './articles';

export const handlers = [
  // Intercepts "GetNewsArticles" graphql query.
  graphql.query('GetNewsArticles', () => {
    return HttpResponse.json({
      data: {
        // Return MOCK_ARTICLE_LIST_DATA as the "articles" root-level property.
        articles: MOCK_ARTICLE_LIST_DATA,
      },
    });
  }),
];

Add Mock Service Worker & Apollo Client to Storybook

With our mocked request and response in place, we now need to configure Mock Service Worker to intercept any requests for GetNewsArticles made in Storybook and resolve them using our mocked handler. Storybook has a convenient add-on for Mock Service Worker that handles the configuration and initialisation of the service worker for us.

Install the storybook-msw-addon using the following command:

yarn add --dev storybook-msw-addon

And register storybook-msw-addon to ./storybook/main.ts

const config: StorybookConfig = {
  // ...config
  addons: [
    // ...addons
    getAbsolutePath('storybook-msw-addon'),
  ],
};

export default config;

Then update our ./storybook/preview.tsx to initiate the Mock Service Worker and include the mswLoader:

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

// initialise MSW addon.
initialize({
  onUnhandledRequest: 'bypass',
});


// provide the MSWloader globally.
export const loaders = [
  //...loaders
  mswLoader
];

After running yarn storybook and viewing the browser’s console you should see the message: [MSW] Mocking enabled. This indicates that the service worker is in place and listening for any requests to intercept.

Finally we must now wrap our stories with the MockApolloProvider, enabling our components to interact with the mock API. Add the following code to ./storybook/preview.tsx:

// ...imports
import { MockApolloProvider } from '../src/mocks/MockApolloProvider';

export const decorators = [
  // ...decorators
  (renderStory: any) => <MockApolloProvider>{renderStory()}</MockApolloProvider>
];

Using the Mock GraphQL API

With the setup behind us, all that’s left for us to do is configure the ArticleList component to make an API request to fetch the articles.

In GraphQL we describe our expected response in a query declaration. Let’s add a declaration to our src/components/ArticleList.tsx for the GetNewsArticles query:

import { gql } from '@apollo/client'

const GET_NEWS_ARTICLES = gql`
  query GetNewsArticles {
    articles {
      id
      date
      slug
      title
      imageUrl
    }
  }`;

Then we can use Apollo’s useQuery hook to execute the API request in src/components/ArticleList.tsx:

// ...imports
import { gql, useQuery } from '@apollo/client'
import { Container, SimpleGrid } from '@mantine/core';

import { ArticleCard } from './ArticleCard';

// ...GET_NEWS_ARTICLES

type Article = {
  id: number;
  slug: string;
  date: string;
  title: string;
  imageUrl: string;
};

export const ArticleList = () => {
  const { data, loading, error } = useQuery(GET_NEWS_ARTICLES);
  if (loading) return 'Loading...';
  if (error) return `Error! ${error.message}`;

  return (
    <Container py="xl">
      <SimpleGrid cols={{ base: 1, sm: 2 }}>
        {data.articles.map((article: Article) => (
          <ArticleCard key={article.id} {...article} />
        ))}
      </SimpleGrid>
    </Container>
  );
};

In the above example code, the useQuery hook is providing us with the returned data object, a loading boolean, and an error object. We’re then able to use these properties to conditionally render either a loading indicator, error notice, or the list of news articles.

We’ll also want to update our src/components/ArticleList.stories.tsx to reflect the above changes:

import type { Meta, StoryObj } from '@storybook/react';

import { handlers } from '../mocks/handlers';
import { ArticleList } from './ArticleList';

const meta: Meta<typeof ArticleList> = {
  component: ArticleList,
};

export default meta;
type Story = StoryObj<typeof ArticleList>;

export const Default: Story = {
  render: () => <ArticleList />,
};

Default.parameters = {
  msw: {
    handlers
  },
};

Note that we are now referencing the Mock Service Worker handlers. Fire up your Storybook and we should see something like this:

ArticleList component loading mock data in Storybook

And there we have it, a complete news article list that mimics the behaviour of a production ready component with no need for a deployed API to be in place. Of course we’ve only scratched the surface here of what’s possible when mocking out an API using Mock Service Worker. Why not go ahead and look at extending this example with more advanced GraphQL operations?

If you’re looking to get more from your prototyping efforts, we can help.

Services discussed

Ben Derham avatar Ben Derham avatar

Ben Derham

@benderham88 (opens in new window)

Front-end Developer. Powered by metal and coffee. Supported by dogs.

A photo of Jed Watson & Boris Bozic together

We’d love to work with you

Have a chat with one of our co-founders, Jed or Boris, about how Thinkmill can support your organisation’s software ambitions.

Contact us