Next.js App Router MSW

김 주현·2024년 6월 14일
1
post-thumbnail

디자이너와 백엔드는 기획적인 부분이 얼추 마무리 되면 바로 작업이 가능하지만, 우리의 프론트엔드는 디자인이 나와야 진행할 수 있고, API가 완료가 되어야 기능을 구현할 수 있어요.

그렇기 때문에, 서비스 개발의 후반부를 맡게 되는 프론트엔드의 역할 상 디자인을 기다리고 API를 하염없이 기다리는 일들이 비일비재합니다.

그렇다면 언제까지나 기다려야 할까요? 물론, 당연히 그래야 하지만(...) 마감날에 쫓기는 건 결국 프론트엔드기 때문에 방법을 찾아야 합니다.

오늘은 그 중에서 API 응답을 임시로 받는 Mocking에 대해서 알아보려고 합니다.


Mocking

프론트엔드는 백엔드 API에 의존적입니다. 백엔드에서 API 개발를 완료해야 작업을 진행할 수 있기 때문이에요. 하지만, 위에서 언급했듯이 마감에 쫓기는 프론트엔드는 하염없이 기다릴 수 없어요.

어차피 받아올 응답값은 정해져있다면, 일단 서버에 들어있는 데이터가 아니더라도 임시로 가짜 데이터를 넣어 로직을 처리해볼 수 있지 않을까요?

요런 아이디어에서 나온 것이 바로 Mocking입니다. 요청에 대해서 일단 가짜 데이터를 응답하는 거죠.

그러다가 백엔드의 API가 완료되면 해당 Mocking을 바꿔치기합니다. 그러면 백엔드 API가 개발될 때 동안 프론트엔드도 대기를 하는 게 아닌 개발을 할 수 있게 되어 조금 더 마감을 지킬 수 있게 됩니다. 뭐 꼭 지켜진다는 건 아니고(...)


JSON 상하차

가볍게 생각해볼 수 있는 Mocking 방법 중 하나는 직접 응답에 대한 객체를 만들어 반환하는 방법입니다. 예를 들어 다음과 같은 API 스펙이 있다고 해볼게요.

GET /api/v1/posts
Response -> { id, title, contents }[]

그렇다면, 저 Response 응답 형식에 맞춰 json 파일을 생성하는 거죠.

posts.mock.json

[
  { id: 0, title: "포스트 1", contents: "포스트 1 내용" },
  { id: 1, title: "포스트 2", contents: "포스트 2 내용" },
]

이후 /api/v1/posts에 fetch를 하는 것이 아닌, posts.mock.json 파일을 fetch해서 응답을 받아오는 거죠. 만들기도 쉬워보이고 나쁘지 않아보여요.

하지만 이 방법은 나중에 Mocking한 부분들을 바꿔치기할 때 문제가 생겨요. 실제 API 경로로 바꿔야 하고, 네트워크 호출에 따른 서비스 로직도 바꿔야 하며, API가 정상 작동하는지도 확인해야 해요.

그리고 무엇보다 Mocking할 API들이 많다면, 각각에 대해 파일을 생성해주고 호출해주고, 수정할 때 되면 다 수정해주어야 하고... 따라올 귀찮음과 비용이 감당 불가능입니다. 그래서 이런 방식을 은어로 JSON 상하차라고도 부른다고 카더라입니다.


Mock Server

이번엔 조금 더 머리를 써서 구현해봅시다. 응답값 뿐만 아니라 다른 응답 상태도 줄 수 있고, Mocking을 바꾸게 되더라도 API 호출 로직의 변경이 없는 방법은 어떨까요? 서버가 정말 작동하는 것'처럼' 말이죠!

이런 서버를 바로 Mock Server라고 합니다. 따로 가짜 데이터를 주는 서버를 띄워 해당 서버에 요청을 주고 받는 거죠.

그렇게되면 API 경로를 제외하고 실제 API와 연결하듯이 로직을 구현할 수 있습니다. 응답 상태에 따른 처리도 가능하구요.

하지만 역시, 서버를 따로 띄운다는 것에 부담이 있죠. 띄우는 것 뿐만 아니라 서버 코드도 따로 작성해야 하니, 시간이 많이 든다는 단점이 있어요.

또, 결국엔 Mocking을 바꿔치기할 때 API의 주소를 바꾸어 주어야 해요. 이 과정에서 휴먼에러가 발생하면 몇몇개의 API는 그대로 죽어버릴 수도...

그렇다면 API 주소도 로직도 바꾸지 않고 값만 가짜 데이터로 받을 순 없는 걸까요? 그게 되겠냐구요?


MSW

MSW는 프록시의 개념을 사용해서 이를 기막히게 풀어냈습니다.

MSW는 Mock Service Worker라는 준말인데요, 이름에서 알 수 있듯이 Mock Data를 구현할 수 있는 서비스 워커입니다. 서비스 워커에 대한 자세한 설명은 다루진 않겠지만, 짧게 말하면 UI 스레드 외에 다른 스레드를 만들어 기능을 제공하는 녀석 정도로 이해하면 될 것 같아요.

이 서비스 워커에는 fetch라는 이벤트를 받을 수 있는데, 이를 이용해 네트워트 요청을 중간에서 가로챈(intercept) 다음 원하는 Mock Data로 반환하는 아이디어입니다.

즉, 일종의 웹 프록시 역할을 해주는 것이죠! 짱이다!

Next.js에서 MSW

요 MSW는 '서비스 워커'이기 때문에 브라우저에서 동작해야 하지만, NodeJS 환경에서도 http나 https 패키지를 이용해서 비슷한 기능을 지원하게 만들었다고 해요.

그리고 Next.js의 서버 환경은 NodeJS죠! 그 말은 즉, MSW를 이용해서 해당 요청을 가로챌 수 있다는 말이겠습니다. RSC에서 fetch는 Next.js의 서버에서 이루어질 것이고, MSW는 서버에서 이루어지는 해당 fetch를 가로챌 수 있겠네요.

이 포스트에서는 서버쪽에서 이루어지는 통신를 가로채는 것이 목적이므로, 브라우저에서 요청하는 fetch에 대해서는 다루지 않을게요.

먼저 MSW에 필요한 파일 먼저 작성해줄게요.

src/mocks/server.ts

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

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

src/mocks/handlers.ts

import { http, HttpResponse } from 'msw';

export const handlers = [
  http.get('https://example.com/user', () => {
    return HttpResponse.json({
      id: 'c7b3d8e0-5e0b-4b0f-8b3a-3b9f4b3d3b3d',
      firstName: 'John',
      lastName: 'Maverick',
    });
  }),
];

완전 간단하죠? Next.js에서 동작하게 될 Server Instance를 생성하고, Mocking할 URL에 대해 어떤 응답을 제공할 것인지만 작성하면 MSW가 동작하는 데 필요한 환경을 만들어주었어요.

다만, 이렇게만 작성하면 동작은 하지 않아요. 그도 그럴 것이 'server'에 대해 어떤 호출도 해주고 있지 않으니까요. 이 server는 어떻게 등록해주어야 할까요?

Next.js에서는 Instrumentation라는 기능을 제공하고 있어요.

Instrumentation is the process of using code to integrate monitoring and logging tools into your application. This allows you to track the performance and behavior of your application, and to debug issues in production.

Instrumentation(계측)이란 코드를 사용하여 애플리케이션에 모니터링 및 로깅 도구를 통합하는 과정이에요. 이를 통해 애플리케이션의 성능과 동작을 추적하고, 프로덕션 환경에서 발생하는 문제를 디버깅할 수 있게 도와줘요.

뭐,, 그렇다고 하는데, 이 Instrumentation라는 기능에 대해서 중요한 건, Next.js 서버가 최초 구동될 때 NodeJS 환경에서 어떤 함수를 호출시켜 준다는 점이에요. 우리는 그 어떤 함수에서 MSW 서버를 열어줄겁니다.

이 기능을 쓰기 위해선 Next.js의 Config를 수정해주어야 해요.

next.config.mjs

const nextConfig = {
  experimental: { instrumentationHook: true },
};

그 다음, '프로젝트의 루트'에 instrumentation.ts라는 이름으로 작성합니다. 저의 경우엔 src를 사용하고 있기 때문에 src에 넣어주었습니다.

src/instrumentation.ts

export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    const { server } = await import('./__mocks__/server');
    server.listen({ onUnhandledRequest: 'bypass' });
  }
}

MSW의 server까지 열었습니다! 그렇다면 RSC에서 fetch를 한번 진행해볼게요.

export default async function Home() {
  const result = await fetchSomething();

  return (<></>)
}

async function fetchSomething() {
  const result = await fetch('https://example.com/user');

  console.log(await result.json());
}

우와! 아주 잘 받아와졌군요! 이제 백엔드의 API 그대로 목업 데이터를 쓸 수 있게 되었어요.


생각할 점: HMR 미지원

handlers.ts 파일을 변경해도 한 번 등록된 MSW Server는 해당 파일을 다시 불러와 등록하지 않기 때문에, Next.js 서버를 다시 띄워야 합니다. 귀찮아!

그래서 실시간으로 적용되게끔 @mswjs/http-middleware 통해서 따로 mock 서버를 띄우는 방식도 존재하는데, 이건 API를 localhost로 쏴야 하는 단점이 존재해요. 물론, 환경 변수 주입으로 baseURL을 바꿔주면 되긴 합니다만, 귀찮아!

개인적으로 느끼는 MSW 가장 큰 장점은 요청하는 네트워크 로직을 API 경로를 포함해 그대로 유지하면서도, 목업 데이터를 넘겨주는 것이라고 생각해요. 그러므로 실시간으로 handler를 반영하진 못 하더라도 instrumentation Hook을 사용할 것 같습니다.

profile
FE개발자 가보자고🥳

0개의 댓글