본문 바로가기

Next.js

[Library] API개발이 아직, MSW

0. Mocking, Service Worker

Service Worker : 웹 응용 프로그램, 브라우저, 그리고 (사용 가능한 경우) 네트워크 사이의 프록시 서버 역할을 합니다.

서비스 워커의 개발을 통해서 효과적인 오프라인 경험 생성, 네트워크 요청을 가로채서 네트워크 사용 가능 여부에 따른 응답 전달, 서버의 자산 업데이트, 푸시 알람과 백드라운드 동기화 API 로의 접근이 가능합니다.

 

 

1. MSW

MSW : 서비스워커를 사용하여 HTTP 네트워크 호출을 가로채는 API 모킹 라이브러리

 

브라우저에 기생해서 마치 백엔드API 인척하면서

요청에 가짜 데이터를 응답해주는 녀석 

 

도입 사례

1. 프론트엔드 UI개발과 백엔드 API개발이 동시에 진행될 때! (백엔드API가 없다!)

(특히! 백엔드의 API 제작이 느릴 때 찰떡!)

2. 테스트 실행할 때! (백엔드API가 있지만!)

실제 백엔드 대신 훨씬 빠르고 안정적인 가짜 API를 사용해보자! 

 

장점

1. 모킹이 네트워크 단에서 일어나기 때문에 실제 API와 통신하듯 코드 작성이 가능하다.

즉, 추후 실제 API로의 교체가 쉽다.

2. REST API 모킹과 GraphQL API 모킹을 모두 지원한다.

 

 

 

2. setting

1. msw 설치 : npm i msw --save-dev

 

2. service worker 등록 : npx msw init public/ --save

msw라이브러리 실행시켜서 public 폴더에 worker를 등록하는 코드

 

3. worker 설정 : 

최상단 혹은 src폴더에 mocks폴더를 만들고 3개의 파일을 만들어야 한다.

 

browsers.ts는 작성한 핸들러가 브라우저에서 작동할 수 있도록 설정하는 파일이다.

worker 인스턴스를 생성하고, 요청 핸들러를 정의한다.

// mocks/browser.ts

import { setupWorker } from "msw/browser";
import { handlers } from "./handlers";

const worker = setupWorker(...handlers);

export default worker;
// mocks/handlers.ts

import { http, HttpResponse } from "msw";

export const handlers = [];
// mocks/http.ts

import { setupServer } from "msw/node";
import { handlers } from "./handlers";

export const server = setupServer(...handlers);

 

 

4. worker 실행 :

당연히 프로젝트 전체에서의 worker 실행을 위해서는

app/layout.ts의 children를 worker가 감싸는 형태가 되어야 한다.

// app/layout.ts

import type { Metadata } from "next";
import "./globals.css";
import { MSWProvider } from "./_components/MSWComponent";

if (
  process.env.NEXT_RUNTIME === "nodejs" &&
  process.env.NODE_ENV !== "production"
) {
  const { server } = require("@/mocks/http");
  server.listen();
}

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body>
        <MSWProvider>{children}</MSWProvider>
      </body>
    </html>
  );
}
// app/_components/MSWComponents.tsx

"use client";

import { Suspense, use } from "react";
import { handlers } from "@/mocks/handlers";

const mockingEnabledPromise =
  typeof window !== "undefined"
    ? import("@/mocks/browser").then(async ({ default: worker }) => {
        if (process.env.NODE_ENV === "production") {
          return;
        }
        await worker.start({
          onUnhandledRequest(request, print) {
            if (request.url.includes("_next")) {
              return;
            }
            print.warning();
          },
        });
        worker.use(...handlers);
        (module as any).hot?.dispose(() => {
          worker.stop();
        });
        console.log(worker.listHandlers());
      })
    : Promise.resolve();

export function MSWProvider({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  // If MSW is enabled, we need to wait for the worker to start,
  // so we wrap the children in a Suspense boundary until it's ready.
  return (
    <Suspense fallback={null}>
      <MSWProviderWrapper>{children}</MSWProviderWrapper>
    </Suspense>
  );
}

function MSWProviderWrapper({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  use(mockingEnabledPromise);
  return children;
}

 

 

 

3. 요청 핸들러 작성

가로챌 준비 끝

어떤 요청에 어떤 응답을 할지에 대한 정보를 나타내는

요청핸들러를 작성하자

// mocks/handlers.ts

import { http, HttpResponse } from "msw";

const User = [
  { id: "a", nickname: "aaa", image: "/aa.jpg" },
  { id: "b", nickname: "bbb", image: "/bb.jpg" },
  { id: "c", nickname: "ccc", image: "/cc.jpg" },
];

const baseUrl = process.env.NEXT_PUBLIC_BASE_URL;

export const handlers = [
  http.get(`${baseUrl}/data`, () => {
    return HttpResponse.json(User[0]);
  }),

  http.post(`${baseUrl}/api/logout`, () => {
    console.log("로그아웃");
    return new HttpResponse(null, {
      headers: {
        "Set-Cookie": "connect.sid=;HttpOnly;Path=/;Max-Age=0",
      },
    });
  }),

  http.post(`${baseUrl}/api/login`, () => {
    console.log("로그인");
    return HttpResponse.json(User[1], {
      headers: {
        "Set-Cookie": "connect.sid=msw-cookie;HttpOnly;Path=/",
      },
    });
  }),

  http.post(`${baseUrl}/api/logout`, () => {
    console.log("로그아웃");
    return new HttpResponse(null, {
      headers: {
        "Set-Cookie": "connect.sid=;HttpOnly;Path=/;Max-Age=0",
      },
    });
  }),

  http.post(`${baseUrl}/api/users`, async ({ request }) => {
    console.log("회원가입");
    // return HttpResponse.text(JSON.stringify('user_exists'), {
    //   status: 403,
    // });
    return HttpResponse.text(JSON.stringify("ok"), {
      headers: {
        "Set-Cookie": "connect.sid=msw-cookie;HttpOnly;Path=/",
      },
    });
  }),
];

 

 

 

 

요약하자면...

초기설정은 npm > npx > mocks에 파일 셋 > MSW컴포넌트 제작 > children에 감싸기

작성은 mocks에 만든 handlers.ts에 

'Next.js' 카테고리의 다른 글