나는 서버가 없어도 개발해 - MSW

Jaewoong2·2022년 8월 9일
3

MSW 란,

프론트엔드 개발을 하다보면 네트워크 통신을 하고 싶을 것 입니다.

이러한 네트워크 통신을 하기 위해서 서버에서 제공하는 API 가 있어야 하는데 백엔드 서버를 만들지 못하거나 백엔드 개발자가 API 를 만들어주기를 기다리고 있던 경험 다들 있을 거에요..?

이럴때, 보통 목업 데이터만을 사용해서 먼저 프론트엔드 개발을 하고 서버 API 가 나오고 난 후, 비동기 네트워크 통신을 하셨을 것입니다.

이럴 때, API 스펙이 어느정도 정해져 있으면 빠르게 목업 서버를 만들고 네트워크 통신을 도와 줄 수 있는 라이브러리가 있습니다.

MSW즉, Mock Service Worker는 말 그대로 가짜 서버 역할을 해서 네트워크 레벨에서 가짜 데이터를 주고 받을 수 있는 라이브러리 입니다.

최신 브라우저 WEB API 중 하나인 service worker 를 이용 하여 네트워크 통신을 해당 msw가 가로채 가짜 데이터 통신을 시켜주게 됩니다.

출처: MDN (https://developer.mozilla.org/ko/docs/Web/API/Service_Worker_API)

서비스 워커는 연관된 웹 페이지/사이트를 통제하여 탐색과 리소스 요청을 가로채 수정하고,
리소스를 굉장히 세부적으로 캐싱할 수 있습니다.
이를 통해 웹 앱이 어떤 상황에서 어떻게 동작해야 하는지 완벽하게 바꿀 수 있습니다.

이러한 msw 를 사용하는것에 가장 큰 이점은 백엔드 API와 네트워크 통신하는 것과 동일하게 비동기 통신 코드를 작성할 수 있다는 것 입니다.

- MSW 공식 홈페이지
Seamlessly reuse the same mock definition for testing, development, and debugging.

"MSW를 통해 작성한 비동기 코드를 그대로 사용 할 수 있다" 는 아래와 같은 장점을 가지고 있습니다

  • 생산성이 높아진다
  • 개발을 할 때 실제로 예상되는 네트워크 통신들을 만들어서 볼 수 있다
    - 예를 들어, 네트워크 통신에 딜레이를 줘서 스켈레톤 이미지, 로딩바 등에 대한 작동을 확인 할 수 있습니다
  • MSW 를 통해 테스팅 할 수 있다

MSW 사용 해보기

MSW 는 서비스 워커를 통해서 작동하기 때문에 라이브러리 및 프레임워크에 종속적 이지 않습니다. 사용하시는 어떠한 방법으로 리엑트를 설치해서 사용해보세요 :)

MSW 라이브러리 설치

npm i -D msw

yarn add -D msw

MSW 관련 서비스 워커 설치 하기

npx msw init public/ --save

해당 명령어를 사용하면 이런식으로 서비스 워커가 생기는 것을 볼 수 있습니다.

MSW 관련 파일 생성하기

# mocks 폴더 생성
mkdir src/mocks
# msw 를 다루는 코드를 작성할 파일 생성
touch src/mocks/handlers.ts
# msw 의 서비스 워커와 handler를 연동 시키는 코드를 작성할 파일 생성
touch src/mocks/browser.ts

MSW 는 REST APIGraphQL API 에 관한 모킹 API를 제공 합니다. 이 글에서는 REST API 를 다루도록 하겠습니다.

MSW Handler 작성하기

import { rest } from 'msw'

export const handlers = []

rest method

  • rest.all() : 어떠한 RESTAPI Method 든 다 요청을 받을 수 있는 msw 만의 method 입니다.
  • rest.get()
  • rest.post()
  • rest.put()
  • rest.patch()
  • rest.delete()
  • rest.options()

hanlders

mswrest api 들은RestHandler 타입을 가지고 있는데 이러한 핸들러들을 모아둔 것 입니다.

나중에, 브라우저 서비스 워커에 이 핸들러를 연동할 것 입니다.

rest api 작성 하기전에

rest.get(path, resolver)
rest.post(path, resolver)

처럼 method 의 첫번째 인자로 path, 두번 째 인자로 request, response, context 를 인자로 갖는 resolver callback 를 넣어줍니다.

예제) [POST] "/login" { body: { username: string } }

let user = {
  name: '',
}

//request:	Information about the captured request.
//response:	Function to create the mocked response.
//context:	Context utilities specific to the current request handler.
type ReqBody = { username: string }

rest.post('/login', async (req, res, ctx) => {
  // 클라이언트에서 body 에 username 를 string 형태로 보내면,
  // 해당 msw에서 req.json() 으로 받아 클라이언트에서 보낸 body를 받아올 수 있습니다.
  // 그외의 request property 는 https://mswjs.io/docs/api/request 에서 확인 가능 합니다
  const { username } = await req.json<ReqBody>()
  user.name = username

  if (username) {
    return res(ctx.status(200))
  }
  
  return res(
    ctx.status(403),
    ctx.json({
      errorMessage: '유저 이름을 등록 해주세요',
    })
  )
}

해당 코드는, [POST] 요청으로 /login 의 엔드포인트를 갖는 api 를 작성 한 것 입니다.

클라이언트에서 보낸 body 값을 받아 `username` 을 제대로 받아 오게 되면,
`user.name` 을 변경 시키고 `status=200` 을 응답 합니다.

제대로 받지 못하면.
`status=403` 을 응답하고
`errorMessage` 를 response data 에 담아 보냅니다.

MSW 핸들러를 서비스 워커와 연결하기

1. 워커 설정하기

// src/mocks/brower
import { setupWorker, SetupWorkerApi } from 'msw'
import { handlers } from './handlers'

export const worker: SetupWorkerApi = setupWorker(...handlers)

워커에 핸들러들을 담아서 setup 해줍니다.

2. 워커 실행하기

// src/main.tsx

import { worker } from './mocks/browser'

if (process.env.NODE_ENV === 'development') {
  // eslint-disable-next-line global-require
  worker.start({ onUnhandledRequest: 'bypass' })
}

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
)

연결 확인 🚀

콘솔 창을 열면 아래와 같은 이미지를 확인 할 수 있습니다

서버를 이용할 클라이언트 만들기

  • 사용 라이브러리: React-Query, Tailwind-css

유저 이름 등록 폼

// src/features/auth/UserForm.tsx

import React from 'react'
import useInput from '../hooks/useInput'
import { useLogin } from '../hooks/useLogin'

const UserForm = () => {
  const [username, setUserName, handleChangeUsername] = useInput()
  const { handleFormSubmit, error: loginError } = useLogin({
    onSettled: () => {
      setUserName('')
    },
  })

  return (
    <>
      <form
        className="w-2/5 h-40 flex justify-center flex-col mx-auto px-5"
        onSubmit={handleFormSubmit({ username })}
      >
        <input
          className="w-3/5 border rounded-lg p-2"
          placeholder="유저이름을 등록해주세요"
          value={username}
          onChange={handleChangeUsername}
        />
        <button
          type="submit"
          className="flex border w-fit p-3 mt-1 rounded-lg bg-blue-500 hover:bg-blue-400"
        >
          유저 등록
        </button>
      </form>
      <p className="w-2/5  flex justify-center flex-col mx-auto px-5 text-red-400">
        {loginError?.response?.data.errorMessage}
      </p>
    </>
  )
}

export default UserForm

클라이언트) 유저 등록 Query API

import axios, { AxiosError, AxiosResponse } from 'axios'
import { useCallback } from 'react'
import { useMutation, UseMutationOptions } from 'react-query'

type LoginRequestVariables = {
  username: string
}

const loginRequest = async ({ username }: LoginRequestVariables) => {
  const response = await axios.post('/login', { username })
  return response
}

type MutationOptions = Omit<
  UseMutationOptions<
    AxiosResponse<any, any>,
    AxiosError<{ errorMessage: string }, any>,
    LoginRequestVariables,
    unknown
  >,
  'mutationFn'
>

export const useLogin = (options?: MutationOptions) => {
  const mutation = useMutation<
    AxiosResponse,
    AxiosError<{ errorMessage: string }>,
    LoginRequestVariables
  >(loginRequest, { ...options })

  const handleFormSubmit = useCallback(
    ({ username }: LoginRequestVariables) =>
      (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault()
        mutation.mutate({ username })
      },
    []
  )

  return { ...mutation, handleFormSubmit }
}

msw 를 사용하면 모킹 서버를 위한 데이터 요청 코드 를 따로 작성 할 필요 없이,
모킹 서버를 위한 데이터 요청, 실제 서버를 위한 데이터 요청 모두 동일한 코드로 할 수 있습니다.

제대로 작동 하는지 확인하기


유저 등록을 클릭하게 되면 콘솔 창에서 아래와 같이 확인 할 수 있습니다.

저희가 작성한 모킹서버가 정상적으로 작동 함을 확인 할 수 있었습니다 :)

오류 내보기

아무 값 없이 유저 등록을 하면 에러 메세지가 보여집니다. 콘솔에는 어떻게 찍혔는지 확인 해봅시다

의도 한 대로 status 403 을 받았음을 확인 할 수 있습니다.

결론

백엔드 개발자가 아니라면, 백엔드 서버를 따로 만들 시간이 없다면, MSW 라이브러리를 사용하는 것은 너무 좋은 선택인것 같습니다

저 또한, 클라이언트 개발이 빨리 되어 서버가 나오기 전에 데이터 값에 따른 변화를 확인 해야 했고 이를 위해 msw 를 도입 하였습니다.

msw 를 사용해 Suspense, 에러 핸들링 등을 서버 없이 구현 할 수 있었고, 해당 비동기 통신 코드를 실제 서버에서 큰 코드 수정 없이 쓸 수 있었습니다

프론트엔드 개발을 하면 계속해서 msw 를 사용 하게 될 것 같습니다.

profile
DFF (Development For Fun)

0개의 댓글