4

I'm integrating a mock API which basically send a response object with a set of messages that are supposed be shown in the UI(Chatbox) along with Username, user pic and so on.

I'm having some trouble setting up the msw 1.1.0 with Next JS 13.2.1 (Experimental app directory with layouts) because of the breaking changes. However this is my code.

My code is set up like this so far

src/app/(user)/live-view/page.tsx

import React from "react";
import ThreadPostList from "../../components/threadList";
 import { worker } from '../../../mocks/browser';

if (process.env.NODE_ENV === 'development') {
  worker.start();
}

async function getMessages() {
  const response = await fetch("http://localhost:3000/api/messages");
  // const data = await response.json();
  return response;
}


async function LiveViewPage() {
  const messages = await getMessages();
  const convertedMessages = Object.keys(messages);
  // console.log(convertedMessages, "ConvertedMessages");
  
  Object.values(messages).forEach(val => console.log(val));

  
  return (
    <div className="border-t border-gray-200 py-4 divide-y divede-gray-200">
      <ThreadPostList />

    
     
    </div>
  );
}

export default LiveViewPage;

And my Msw mock API is under

src/mocks/handler.ts

import { rest } from "msw";
import { UserWithMessages } from "./types";

const mockData: UserWithMessages[] = [
  {
    user: {
      id: "user1",
      name: "John Doe",
      profilePic: "https://example.com/user1.jpg",
    },
    messages: [
      {
        id: "msg1",
        senderId: "user1",
        text: "Hey, how are you?",
        createdAt: new Date("2022-01-01T12:00:00Z"),
      },
      {
        id: "msg2",
        senderId: "user1",
        text: "Did you get my email?",
        createdAt: new Date("2022-01-02T12:00:00Z"),
      },
    ],
  },
  {
    user: {
      id: "admin",
      name: "Admin",
      profilePic: "https://example.com/admin.jpg",
    },
    messages: [],
  },
];


export const handlers = [
  rest.get<UserWithMessages[]>("/api/messages", (req, res, ctx) => {
    return res(ctx.status(200), ctx.json(mockData));
  }),
];

And finnaly my browser.ts is under

src/mocks/browser.ts

"use client"

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

export const worker = setupWorker(...handlers);

when I run the server it's throwing an error

Error: Cannot access worker.start on the server. You cannot dot into a client module from a server component. You can only pass the imported name through.

Where and How do I start my worker? Do I have to create a file under mocks and call the server there and import that component in my page.tsx?Any help would be appreciated thanks

Mahijendra
  • 335
  • 5
  • 15

1 Answers1

2

I've been fighting with getting Mock Service Worker to run consistently with NextJS within the app directory structure since it moved into stable release. I've had some level of success, but I suspect it isn't the ideal solution. We are still waiting on a legitimate example implementation from the NextJS team.

We can't call the worker on a server-rendered component because of the exact error you are seeing. Instead, I've moved my fetch calls into a shared service that is just typescript:

./services/api-service.ts

async function initMocks() {
  if (typeof window === 'undefined') {
    const { server } = await import('./src/mocks/server')
    await server.listen({ onUnhandledRequest: 'bypass' })
  } else {
    const { worker } = await import('./src/mocks/browser')
    await worker.start({ onUnhandledRequest: 'bypass' })
  }
}

// Note the change in ENV var name here
// https://nextjs.org/docs/pages/building-your-application/configuring/environment-variables#bundling-environment-variables-for-the-browser
if (process.env.NEXT_PUBLIC_MOCK_APIS === 'enabled') {
  initMocks()
}

export const makeApiRequest = async (path: string) => {
  try {
    const resp = await fetch(path)
    if (resp.ok) {
      const result = await resp.json()
      return result
    }
  } catch (err: any) {
    // handle errors
  }
}

I don't know why, but implementing MSW at a lower level like this, and outside of an actual react component has been more successful for me than trying to initialize it at the page or layout level, as it's implemented in the NextJS pages directory examples.

This would change your page component to look more like this at the top:

src/app/(user)/live-view/page.tsx

import React from "react";
import ThreadPostList from "../../components/threadList";
import { makeApiRequest} from './services/api-service';

async function getMessages() {
  const response = await makeApiRequest("http://localhost:3000/api/messages");
  return response;
}

One more thing I notice here is that your handler is catching on a relative path, which might trip things up on the server side as well. The server handler lacks the context of a root URL and I've had better success with either full paths in there or path matching. So either:

export const handlers = [
  rest.get<UserWithMessages[]>("http://localhost:3000/api/messages", (req, res, ctx) => {
    return res(ctx.status(200), ctx.json(mockData));
  }),
];

or:

export const handlers = [
  rest.get<UserWithMessages[]>("*/api/messages", (req, res, ctx) => {
    return res(ctx.status(200), ctx.json(mockData));
  }),
];

The latter might come in handy if you want to use MSW to run Jest tests or something in a deployed environment or somewhere other than your machine.

Technically, in your problem and in any app that follows the best practices outlined in the NextJS documentation, we wouldn't need the browser implementation of MSW at all since all data would be fetched in server components and used to hydrate the client. However, I left it in here to provide some consistency when comparing to previous implementations.

At the time I wrote this I'm working with msw@1.2.1 and next@13.4.3

Jason Gross
  • 260
  • 1
  • 10