Mocking APIs with Mirage
Tags: webdev, mirage, api
Published: 2022-11-06 14:22:06


by [Sachin Chaurasiya](https://blog.openreplay.com/authors/sachin-chaurasiya)


When working with full-stack applications, we often have to wait for the backend APIs to build the needed endpoints, affecting productivity and project timelines. Mocking API will solve this problem, and we can build complete front-end features even if the API does not exist.

In this tutorial, we will build a simple "phone book" React application to show how we work with Mirage mock API.


## What is Mirage?

[Mirage](https://miragejs.com/) is an API mocking library that lets you create the backend APIs with seed data.

Unlike other mocking libraries, Mirage lets you recreate dynamic scenarios, which are only possible on the production server.

Mirage creates a fake server that runs in the client and can be used for both development and testing (unit and End to End).

Some of the best features of Mirage Includes

- Routes to handle HTTP requests
- A database and models for storing data and defining relationships
- Factories and fixtures for stubbing data, and
- Serializers for formatting HTTP responses

Alright, now we have some idea of what Mirage is and its features, let's build a simple application to see things in action.

## Creating our React application

Create a new folder, `phone_book`, and open it in your VSCode editor.

```shell
mkdir phone_book
```
After opening the `phone_book` folder, now open the terminal and run,

```shell
npx create-react-app .
```
The above command will create a react application in the same folder.

Now we have the basic setup ready, let's set up Mirage.

## Creating our Mirage Server

Mirage is a third-party library, so we will have to install it in our application either by `npm` or `yarn`.

```shell
# Using npm
npm install --save-dev miragejs

# Using Yarn
yarn add --dev miragejs
```

We have successfully installed Mirage into our application; now, we have to create a server that will handle the routing of our API endpoints.

Create a `server.js` file in the `src` folder. This file will have all the codes for our Mock API.

```shell
# src/server.js

touch src/server.js
```

Mirage provides a method `createServer` to create a fake server. It accepts a bunch of configs to create a fake server. In this tutorial, we will be using a few of them.
- environment 
- namespace
- routes
- seeds
- models

```javascript
# src/server.js

import { createServer, Model } from "miragejs";

const DEFAULT_CONFIG = {
  environment: "development",
  namespace: "api",
};

export const makeServer = ({ environment, namespace } = DEFAULT_CONFIG) => {
  return createServer({
    environment,
    namespace,
    models: {
      contact: Model,
    },
  });
};
```

Here we are creating a fake server with a `development` environment and `api` namespace. We are also adding one model, `contact,` which will have the data structure of the phone book contacts.

Mirage lets you create a server in different environments, so if you are in development mode, you can pass the environment as `development` to load the server with some seed data.

You can pass the environment as a `test` for testing, so it will not load the seed data in the tests.
You can create data according to your test case.

Alright, we have a basic setup for the server. The next step is to create seeds and routes. 

## Creating API routes and seed data

We will use the [fakerjs](https://fakerjs.dev/) package to create seeds. Install it by running:

```shell
npm install @faker-js/faker --save-dev

#OR

yarn add @faker-js/faker --dev
```

Our phone book application will need the following routes:

- GET `/api/contacts` to fetch all contacts records
- GET `/api/contacts/:id` to fetch a single contact record
- POST `/api/contacts` to create a new contact record
- PATCH `/api/contacts/:id` to update an existing contact record
- DELETE `/api/contacts/:id` to remove an existing contact record

After adding seeds and routes, `server.js` will be like this.

```javascript
# src/server.js

import { createServer, Model } from 'miragejs';
import { faker } from '@faker-js/faker';

const DEFAULT_CONFIG = {
  environment: 'development',
  namespace: 'api',
};

export const makeServer = ({ environment, namespace } = DEFAULT_CONFIG) => {
  return createServer({
    environment,

    namespace,

    models: {
      contact: Model,
    },

    seeds(server) {
      const LIST_LENGTH = 5;

      // loop to create a seed data
      for (let index = 1; index <= LIST_LENGTH; index++) {
        server.create('contact', {
          name: faker.name.fullName(),
          number: faker.phone.number(),
        });
      }
    },

    routes() {
      // fetch all contacts records
      this.get('/contacts', (schema) => {
        return schema.contacts.all();
      });

      // fetch a single contact record
      this.get('/contacts/:id', (schema, request) => {
        const id = request.params.id;

        return schema.contacts.find(id);
      });

      // create a new contact record
      this.post('/contacts', (schema, request) => {
        const attrs = JSON.parse(request.requestBody);

        return schema.contacts.create(attrs);
      });

      // update an existing contact record
      this.patch('/contacts/:id', (schema, request) => {
        const newAttrs = JSON.parse(request.requestBody);
        const id = request.params.id;
        const contact = schema.contacts.find(id);

        return contact.update(newAttrs);
      });

      // remove an existing contact record
      this.delete('/contacts/:id', (schema, request) => {
        const id = request.params.id;

        return schema.contacts.find(id).destroy();
      });
    },
  });
};
```

Congratulations, our mock server is ready with initial data and routes. Let's call the `makeServer` function in `index.js` to initiate the server.

Update the `index.js` with the below code.

```javascript
# src/index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { makeServer } from './server';

if (
  process.env.NODE_ENV === 'development' &&
  typeof makeServer === 'function'
) {
  makeServer();
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
```

Here we are checking if the environment is `development` and `makeServer` is a type of function; only then will we call the `makeServer` function.

All right, we have a server running. In the next section, we will set up the front end and access the mock APIs.


<h2>Open Source Session Replay</h2> 

_<a href="https://github.com/openreplay/openreplay" target="_blank">OpenReplay</a> is an open-source, session replay suite that lets you see what users do on your web app, helping you troubleshoot issues faster. OpenReplay is self-hosted for full control over your data._

![OpenReplay](https://blog.openreplay.com/banner-blog_1oYPGT.png)

_Start enjoying your debugging experience - <a href="https://github.com/openreplay/openreplay" >start using OpenReplay for free</a>._




## Setting up the front end

We will use the [Chakra UI](https://chakra-ui.com/) package for the front end. Chakra UI is a simple, modular and accessible component library that gives you the building blocks you need to build your React applications.

Install Chakra UI and its dependencies by running

```shell
npm i @chakra-ui/react @emotion/react @emotion/styled framer-motion

# OR 

yarn add @chakra-ui/react @emotion/react @emotion/styled framer-motion
```

Start the server by running, 
```shell
npm start

# OR 

yarn start
```

Then visit the [http://localhost:3000/](http://localhost:3000/) to view the changes.

Our phone book application will have the following features:

- Users can view the contact list
- Users can create the new contact
- Users can edit the contact
- Users can delete the contact

### Listing all the contacts

Here in the [App.jsx](https://github.com/Sachin-chaurasiya/Phone_Book_MirageJs/blob/main/src/App.jsx) we are fetching the contact list from the `/api/contacts` endpoint and storing it in the `contacts` state. 

```javascript
...
const fetchContacts = useCallback(async () => {
    try {
      setIsLoading(true);
      const response = await fetch('/api/contacts');
      const contactList = await response.json();

      setContacts(contactList.contacts);
    } catch (error) {
      toast({
        title: 'Error while fetching contacts',
        description: error,
        status: 'error',
        duration: 9000,
        isClosable: true,
      });
    } finally {
      setIsLoading(false);
    }
  }, [toast]);
...
```

We also have the `isLoading` state, and we will show the loader when data is fetching; once data is fetched [ContactList](https://github.com/Sachin-chaurasiya/Phone_Book_MirageJs/blob/main/src/components/ContactList.jsx) will be rendered. 
Now start the server, and you will see something like this.

![1 image.png](https://blog.openreplay.com/images/mocking-apis-with-mirage/images/image01.png)

Great, our list view is ready. In the next step, we will create UI for adding new contacts.

### Creating a contact

We will create a [ContactModal](https://github.com/Sachin-chaurasiya/Phone_Book_MirageJs/blob/main/src/components/ContactModal.jsx) component, and it will handle the create contact flow.

[ContactModal](https://github.com/Sachin-chaurasiya/Phone_Book_MirageJs/blob/main/src/components/ContactModal.jsx) will receive two props `isOpen` and `onClose`, isOpen will be responsible for showing/hiding the modal and onClose will be responsible for closing the modal when the user clicks on cancel or after contact gets created.

```javascript
...
const handleCreateContact = async (e) => {
    e.preventDefault();
    const isValid = Object.values(contactErrors).every(
      (value) => value === false
    );
    if (isValid) {
      try {
        const response = await fetch('/api/contacts', {
          method: 'POST',
          body: JSON.stringify(contactData),
        });

        await response.json();
      } catch (error) {
        toast({
          title: 'Error while creating contact',
          description: error,
          status: 'error',
          duration: 9000,
          isClosable: true,
        });
      } finally {
        onClose();
      }
    } else {
      toast({
        title: 'Invalid data',
        description: 'Name or Number is invalid',
        status: 'error',
        duration: 9000,
        isClosable: true,
      });
    }
  };
...
```

Let's try out the create contact flow, click on the `Add Contact` button, fill out the details, and click on the create button to create the contact.

![2 image.png](https://blog.openreplay.com/images/mocking-apis-with-mirage/images/image02.png)


![3 image.png](https://blog.openreplay.com/images/mocking-apis-with-mirage/images/image03.png)

### Updating a contact

We will use the same modal for updating the contact, so the updated ContactModal component will look like this.

Here we have added one more prop, `selectedContact`, and based on this prop we will show the updated view for the contact.

The `handleUpdateContact` will be responsible for updating the contact. In this method, we have used the `PATCH` option to update the contact.

```javascript
...
const handleUpdateContact = async (e) => {
    e.preventDefault();
    const isValid = Object.values(contactErrors).every(
      (value) => value === false
    );
    if (isValid) {
      try {
        const response = await fetch(`/api/contacts/${selectedContact.id}`, {
          method: 'PATCH',
          body: JSON.stringify(contactData),
        });

        await response.json();
      } catch (error) {
        toast({
          title: 'Error while updating contact',
          description: error,
          status: 'error',
          duration: 9000,
          isClosable: true,
        });
      } finally {
        onClose();
      }
    } else {
      toast({
        title: 'Invalid data',
        description: 'Name or Number is invalid',
        status: 'error',
        duration: 9000,
        isClosable: true,
      });
    }
  };
...
```

Now go to the browser and try out the update flow.
 
![4 video.gif](https://blog.openreplay.com/images/mocking-apis-with-mirage/images/image04.gif)

### Deleting a contact

To build the Delete flow, we will need to select the contact id to take that id and make a delete request to delete the contact.

The last step is to pass the required props from the App component to child components and also render the [DeleteContactModal](https://github.com/Sachin-chaurasiya/Phone_Book_MirageJs/blob/main/src/components/DeleteContactModal.jsx) when the contact id is selected.

```javascript
...

const handleDeleteContact = async () => {
    try {
      await fetch(`/api/contacts/${contactId}`, {
        method: 'DELETE',
      });
      onClose();
    } catch (error) {
      toast({
        title: 'Something went wrong!',
        description: 'Error while deleting the contact',
        status: 'error',
        duration: 9000,
        isClosable: true,
      });
    } finally {
      onClose();
    }
  };
...
```

![5 DeleteFlow.gif](https://blog.openreplay.com/images/mocking-apis-with-mirage/images/image05.gif)

And that's it for this topic. Thank you for reading!

## Summary

- We have discussed what Mirage is, its features, and what problems Mirage solves.
- We have created a mock server with API routes and Seed Data
- We have built a `phone_book` application and used the Mock APIs.


## Resources

- [What is Mirage](https://miragejs.com/)
- [Features of Mirage](https://miragejs.com/docs/getting-started/introduction/)
- [React QuickStart Guide](https://miragejs.com/quickstarts/react/develop-a-component/)
- [Github Repository (Source code)](https://github.com/Sachin-chaurasiya/Phone_Book_MirageJs)


> A TIP FROM THE EDITOR: For a different way to mock APIs for testing, look at our [Forever Functional: Injecting For Purity](https://blog.openreplay.com/forever-functional-injecting-for-purity/) article.




[![newsletter](https://blog.openreplay.com/newsletter_Z1P0UIP.png)](https://newsletter.openreplay.com/)