How to create fake API server for react apps with MirageJS
Tags: react, tutorial, webdev, javascript
Published: 2020-10-16 09:23:54

If you are building a web app using `React`, `Vue`, `Angular`, or with any of your favorite front end framework, you need to talk to backend APIs for `CRUD` operations. Let's say you want to build a prototype of the app quickly, but you don't have the backend APIs ready yet, what will do in this case? The best way is to have mock data from a fake server.

How to create mock data, we have so many libraries that can help us achieve this goal, but in this post, I'm considering using `miragejs` with `React`.

Why am I considering this while there are other popular libraries to consider, because of 2 reasons, the first one, you don't have to create/spin another server to load your data for eg: `http://localhost:3001` where your mock server runs, but mirage runs in the same development server and lets you access the data like you are working with real APIs, and the second one, you can use the mirage as your API endpoint to write end-to-end tests using `Cypress`, I didn't even think about other options when I get 2 benefits just creating a mock server with mirage and it offers a great developer experience in my opinion.

You can use it to mock your API endpoints with `react-testing-library` for writing unit test cases too. Please refer the [ documentation](https://miragejs.com/quickstarts/overview/) for more details. 

Let's get started, create a react app using `create-react-app`, and add this to `index.js`. Runs the mock server only during development.
```js
// index.js
import React from "react";
import ReactDOM from "react-dom";

import { makeServer } from "./server";
import UsersLayout from "./users-layout";

// It creates the mock server only in development mode
if (process.env.NODE_ENV === "development") {
  makeServer({ environment: "development" });
}

const App = () => <UsersLayout />;

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
```

Create `server.js` where the real magic happens with less code,
```js
// server.js
import { createServer, Model } from "miragejs";

export function makeServer({ environment = "test" } = {}) {
  let server = createServer({
    environment,

    models: {
      user: Model,
    },

    seeds(server) {
      server.create("user", { id: 1, name: "Bob Jhon" });
      server.create("user", { id: 2, name: "Alice" });
    },

    routes() {
      this.namespace = "api";

      this.get("/users", (schema) => schema.users.all());
      
      // To increment the id for each user inserted,
      // Mirage auto creates an id as string if you don't pass one
      let newId = 3
      this.post("/users", (schema, request) => {
        const attrs = JSON.parse(request.requestBody);
        attrs.id = newId++
        
        return schema.users.create(attrs);
      });

      this.delete("/users/:id", (schema, request) => {
        const id = request.params.id;

        return schema.users.find(id).destroy();
      });
    },
  });

  return server;
}
```

`seeds()` method will seed our user model with some initial data so that we can start using it immediately, you can leave it empty if you want to start with an empty user collection.

Define all your API routes in the `routes()` method and you can define your API namespace with `this.namespace = 'api'` so that you don't have to repeat it in all the routes like for eg: `this.get('/api/users')`. Here I've three routes to `GET`, `POST`, and `DELETE` a user. 

You need to create a model with the help of mirage `Model` and with that, you can access data from `schema`, if you notice carefully, I've created a user model with the name `user` but accessing it as `schema.users.all()`, mirage does create the pluralized collection for us looking into the name of the model, its good practice to keep singular names for your models.

Mirage offers other methods on the schema to `add` and `delete` an item from the collection, see `delete`, and `post` API routes in the code example above.

That's it, let us write React side of the code so that we can consume the mirage's fake API with `fetch` or `axios`, I'm using `fetch` here.
```js
// users-layout.js
import React, { useState, useEffect, useCallback } from "react";
import { useFetch } from "./use-fetch";

export default function UsersLayout() {
  const [users, setUsers] = useState([]);
  const { data, loading: userLoading, error: userError } = useFetch(
    "/api/users"
  );
  const [name, setName] = useState("");
  const [isUpdating, setIsUpdating] = useState(false);

  useEffect(() => {
    if (data) {
      setUsers(data.users);
    }
  }, [data]);

  const onAddUser = useCallback(
    async (e) => {
      e.preventDefault();
      try {
        setIsUpdating(true);
        const res = await fetch("/api/users", {
          method: "POST",
          body: JSON.stringify({ name }),
        });

        const data = await res.json();
        setUsers((users) => users.concat(data.user));
        setIsUpdating(false);
        setName("");
      } catch (error) {
        throw error;
      }
    },
    [name]
  );

  return (
    <>
      <form onSubmit={onAddUser}>
        <input
          type="text"
          onChange={(e) => setName(e.target.value)}
          value={name}
        />
        <button type="submit" disabled={isUpdating}>
          {isUpdating ? "Updating..." : "Add User"}
        </button>
      </form>
      {userError && <div>{userError.message}</div>}
      <ul>
        {!userLoading &&
          users.map((user) => <li key={user.id}>{user.name}</li>)}
      </ul>
    </>
  );
}
```

And a bonus in the above code, I wrote a custom hook to fetch the data `useFetch` from any API endpoints. Let's look at the code for useFetch
```js
// use-fetch.js
import { useEffect, useState, useRef } from "react";

/**
 * Hook to fetch data from any API endpoints
 */
export const useFetch = (url) => {
  const [state, setState] = useState({
    data: null,
    loading: true,
    error: null,
  });
  const isCurrent = useRef(true);

  useEffect(() => {
    return () => {
      isCurrent.current = false;
    };
  }, []);

  useEffect(() => {
    setState((state) => ({ ...state, loading: true }));
    const getData = async () => {
      try {
        const res = await fetch(url);
        const data = await res.json();

        // If calling component unmounts before the data is
        // fetched, then there is a warning, "Can't perform
        // React state update on an unmounted component"
        // it may introduce side-effects, to avoid this, useRef to
        // check for current reference.
        if (isCurrent.current) {
          setState((state) => ({
            ...state,
            data,
            loading: false,
            error: null,
          }));
        }
      } catch (error) {
        setState((state) => ({ ...state, error: error }));
      }
    };

    getData();
  }, [url]);

  return state;
};
```

That's it, with a little effort you are able to mock the data with a fake API server using miragejs. And mirage scales well with large applications too, I've battle-tested this and hope you will find it useful. Give a try on your next project. This is going to save a lot of time during development.

I'll write a follow-up article on how I used miragejs as a backend for `Cypress` end-to-end tests, until then bye, bye.