Why I migrated to msw from json-server
Tags: javascript, mocking, msw
Published: 2021-08-26 13:25:48

## Introduction

At my company we've been using [json-server](https://github.com/typicode/json-server) since the beginnand as it started simple. Now we've reached a point where the customization is just not enough without writting a full blown node server with express. So I was advised to have a look at [Mock Service Worker (msw)](https://mswjs.io/), and I can say that now I have all that I need to mock all our api's.

### json-server

#### Level: I'm too young to die

We started out with a handful of api's which were quite simple, this was very easy to handle with `json-server`, I created a `db.json` file with the api's I wanted to mock:

```json
{
  "auth": {
    "user_id": 60
  },
  "campaigns": [
    { 
      "id": 1,
      "created_at": "2020-05-12T09:45:56.681+02:00",
      "name": "Khadijah Clayton"
    },
    {
      "id": 2,
      "created_at": "2020-05-12T09:45:56.681+02:00",
      "name": "Caroline Mayer"
    },
    {
      "id": 3,
      "created_at": "2020-05-12T09:45:56.681+02:00",
      "name": "Vanessa Way"
    },
    {
      "id": 4,
      "created_at": "2020-05-12T09:45:56.681+02:00",
      "name": "Generation X"
    },
    {
      "id": 5,
      "created_at": "2020-05-12T09:45:56.681+02:00",
      "name": "Mariam Todd (Mitzi)"
    }
  ]
}
```

A `json-server.json` file with the following config:

```json
{
  "host": "localhost",
  "port": 4000,
  "delay": 250
}
```

And a `package.json` script:

```json
"api": "json-server demo/db.json",
```

Running this with `yarn run api` and hitting `localhost:4000/campaigns` would return the list of campaigns, so far so good.

#### Level: Hey, not too rough

Some api's would be nested under a `campaign_id` param i.e. `/campaigns/:campaign_id/tasks`. So introducing [routes](https://github.com/typicode/json-server#add-custom-routes):

`json-server.json`:

```json
{
  "routes": "demo/routes.json",
  "host": "localhost",
  "port": 4000,
  "delay": 250
}
```

`routes.json`:

```json
{
  "/campaigns/:campaign_id/tasks": "/campaigns_tasks"
}
```

This way any hit to `localhost:4000/campaigns/321/tasks` would route to `/campaigns_tasks` in my database file.

#### Level: Hurt me plenty

As you can imagine the database file grew unmanageably big very quick. So introducing [middlewares](https://github.com/typicode/json-server#add-middlewares):

`json-server.json`:

```json
{
  "routes": "demo/routes.json",
  "middlewares": "demo/middleware.js",
  "host": "localhost",
  "port": 4000,
  "delay": 250
}
```

`middleware.js`:

```js
import campaigns from './demo/campaigns.json';

module.exports = function (req, res, next) {
  if (req.method === 'DELETE' || req.method === 'PUT') {
    return res.jsonp();
  }

  if (req.originalUrl === '/campaigns') {
    return res.jsonp(campaigns);
  }

  next();
}
```

This allowed me to separate data into several json chunks *and* allowed me to handle other methods like `DELETE` or `PUT` without the actions editing the database.

#### Level: Ultra-Violence

However the app continued growing and so would the amount of api's backend would deliver that I wanted mocked. So I updated the middleware to handle the urls with regex in order to fine tune the response.

`middleware.js`:

```js
import campaign from './demo/campaign.json';
import tasks from './demo/tasks.json';

module.exports = function (req, res, next) {
  if (req.method === 'DELETE' || req.method === 'PUT') {
    return res.jsonp();
  }

  if (req.originalUrl.match(/\/campaigns\/[0-9]*$/)) {
    return res.jsonp(campaign);
  }

  if (req.originalUrl.match(/\/campaigns\/([0-9]+)\/tasks/)) {
    return res.jsonp(tasks);
  }

  next();
}
```


#### Level: Nightmare!

As the middleware grew larger so did each individual json file, long arrays of hundreds of items were very hard to maintain. So in order to have the data short and dynamic I added [Faker.js](https://github.com/marak/Faker.js/).

`middleware.js`:

```js
import campaign from './demo/campaign.js';

module.exports = function (req, res, next) {
  if (req.originalUrl.match(/\/campaigns\/[0-9]*$/)) {
    const data = campaign();
    return res.jsonp(data);
  }

  next();
}
```

`campaigns.js`:

```js
import faker from 'faker';

const gen = (fn) => {
  const count = faker.random.number({ min: 1, max: 10 });
  return new Array(count).fill(0).map((_, idx) => fn(idx));
};

module.exports = () => {
  faker.seed(32);
  
  return gen(() => ({
    id: faker.random.number(),
    owner_id: faker.random.number(),
    active: faker.random.boolean(),
    budget: faker.random.number(),
    description: faker.lorem.sentence(),
    created_at: new Date(faker.date.recent()).toISOString()
  }));
};
```

### Interlude

So as you can see, we reached a point where it was being harder and harder to maintain. So at this point I was suggested to try out [Mock Service Worker (msw)](https://mswjs.io/).

### MSW

I'm going to skip the setting up part since there are plenty of articles out there [1](https://dev.to/sapegin/modern-react-testing-part-4-cypress-and-cypress-testing-library-1emj), [2](https://dev.to/danieljcafonso/configuring-mock-service-worker-msw-oma), [3](https://dev.to/kettanaito/announcing-mswjs-data-data-modeling-library-for-testing-javascript-applications-3290), [4](https://dev.to/this-is-angular/mocking-with-msw-and-nx-28ab) to link a few plus of course their own documentation which is 👌🏻.

#### Config

I do want to mention thought that I have setup both the browser and node types, because I want the browser to handle the api's via service worker and I also want the specs to read from here via node.

`server.js`

```js
import { setupServer } from 'msw/node';
import { handlers } from './handlers';

// This configures a request mocking server with the given request handlers.
export const server = setupServer(...handlers);
```

`browser.js`

```js
import { setupWorker } from 'msw';
import { handlers } from './handlers';

// This configures a Service Worker with the given request handlers.
export const worker = setupWorker(...handlers);
```

`handlers.js`

```js
export const handlers = [
  ...
]
```

I also had to configure [CRA](https://reactjs.org/docs/create-a-new-react-app.html) to run the `browser.js` on start and `jest` to run the `server.js` for all tests.

#### Removing the redundant

Now there's no need to use regular expressions since within the handlers I can setup the [REST](https://mswjs.io/docs/api/rest) api logic. So removing middleware.js and routes.json.

`handlers.js`

```js
import { rest } from 'msw';
import campaigns from './demo/campaigns.js';
import campaign from './demo/campaign.js';

export const handlers = [
  rest.get('/campaigns', (_, res, ctx) => {
    return res(
      ctx.json(campaigns())
    );
  },
  
  rest.get('/campaigns/:id', (req, res, ctx) => {
    const { id } = req.params;
    
    return res(
      ctx.json(campaign(id))
    );
  },
  
  rest.get('/campaigns/:id/*', (req, res, ctx) => {   
    return res(
      ctx.status(200)
    );
  },
]
```

You can quickly see that this can be separated into several sections, like `campaignHandlers` and others which would make it easier to read.

```js
import campaignHelpers from './handlers/campaigns';

export const handlers = [
  ...campaignHelpers,
  ...others,
]
```

#### Next steps [mswjs/data](https://github.com/mswjs/data)

The next steps I want to work on when I have the time is setting up the data factories, so that I can create items on demand and have a cleaner structure with models.

### Final thoughts

Yes, this article does look more like a `json-server` tut, but I thought it might be useful to show the struggles I went through and *what* made me look for another more versatil solution.

And that's that. Please let me know if you had any similar struggles and if this article was useful to you.