[Next.js 14 / 🔥 Tadak Tadak 🔥] 백엔드 개발자가 API 안 만들어 줬다고 손 놓고 있을겁니까? MSW(Mock Service Worker) 한번 써보세요!
백엔드 팀과 협업하는 프로젝트를 하게 되면 보통 백엔드는 도메인(비지니스 로직)과 DB 설계 작업을 먼저 진행하고 API 작업을 시작합니다. 프런트 팀은 이런 백엔드의 스케줄에 맞춰서 API 작업이 끝날 때까지 스타일 작업 등을 먼저 진행하게 됩니다.
하지만 프론트에서 할 수 있는 작업이 끝났음에도 백엔드의 API 작업이 마무리되지 않아 프론트엔드 팀의 시간이 붕 뜨게 되는 일이 비일비재합니다. 이때의 시간을 효율적으로 사용하기 위해 사용할 수 있는 라이브러리인 MSW에 대해 알아봅시다!
🍎 목차
1. MSW를 사용하는 이유
2. MSW 설치 및 Service Worker의 역할
3. (번외) Next.js Route Handler로 MSW 대체하기
4. Next.js 에서 MSW 사용 시 유의할 점
5. Next.js 14에 MSW 2 도입하기
6. MSW 사용 예제 (+ Server Actions)
7. 마무리
1. MSW를 사용하는 이유
Mock Service Worker is an API mocking library that allows you to write client-agnostic mocks and reuse them across any frameworks, tools, and environments.
MSW는 API 모킹 라이브러리로 클라이언트에 구애받지 않는 임시(가짜) 정보를 작성 가능하게 하고, 이렇게 작성한 임시(가짜) 정보를 어느 프레임워크, 도구 그리고 환경에서든 재사용 가능하게 합니다.
위의 내용은 MSW 공식 홈페이지에 들어가면 보이는 메인 문구입니다.
앞서 말한 것 처럼 백엔드와 함께 프로젝트를 진행하면 백엔드의 API 개발이 끝날때 까지 프론트엔드는 기다려야 하는 상황이 생깁니다.
MSW는 원하는 path에 대한 http 요청을 가로채서 원하는 mock 데이터를 가져다 줍니다. 이로써 백엔드의 API 개발이 더딘 상황에 대처 가능하게 됩니다.
또한 로딩, 에러, 로그인 등의 상황을 확인 할때 일일이 해당 상황을 부러 만들어 확인해야 하는 어려움이 있었는데 이 또한 MSW를 사용해서 해결 가능합니다.

2. MSW 설치 및 Service Worker의 역할
npx msw init public/ --save
npm install msw --save-dev
위와 같이 MSW를 설치해서 초기 세팅하게 되면 public 폴더에 mockServiceWorker.js 파일이 생기게 됩니다.
MSW는 자동으로 mockServiceWorker.js 파일을 브라우저에 설치해 줍니다. ➡︎ 클라이언트 환경에서 실제 api 서버 주소로 보내는 요청을 mockServiceWorker가 가로채서 mock/http 서버로 보냅니다. ➡︎ http 서버는 다시 handler로 연결되어 handler.ts 파일이 실행됩니다.
정리하면 개발 서버일 경우 원래하던 대로 실제 주소로 데이터를 보내주면 MockServiceWorker가 활성화되어 실제 서버 주소로 보내는 요청을 가로채게 됩니다.
3. (번외) Next.js의 Route Handler로 MSW 대체하기
MockServiceWorker(MSW)를 사용하지 않을 경우에는 개발 환경용 주소와 실제 환경용 주소를 따로 넣어줘서 분기처리를 해줘야 합니다.
분기 처리라 함은 예를들어 process.env.NODE_ENV가 development면 개발용 주소로 production이면 배포 주소로 연결해주는 것을 말합니다.
실제로 저번에 진행한 Next.js를 이용한 프로젝트(원데이 히어로)에서 MSW를 도입하려 했으나 SSR를 지원하는 MSW도 App Router 환경에서는 제대로 작동하지 못하는 문제가 있어서 (v1.31 기준) 결국에 Next.js의 기본 기능인 Route Handler를 이용해서 MSW를 대체했습니다.
Route Handler를 사용하면 웹 요청 및 응답 API를 사용하여 원하는 경로에 대한 커스텀한 요청 핸들러를 만들 수 있습니다.
Route Handler의 path를 백엔드의 path와 동일하도록 설정하고, 앞부분의 api url을 백엔드의 것과 Route Hander의 것으로 변경할 수 있도록 구현해서 백엔드의 api가 만들어지기 전까지 .env.local 변수명( = NEXT_PUBLIC_API_MOCKING)을 변경하여 필요할 때마다 mock data를 사용했습니다.
// .env.local
NEXT_PUBLIC_API_MOCKING="enabled"
// enabled일 경우 mock 데이터를 이용할 수 있다.
const apiBaseUrl =
process.env.NEXT_PUBLIC_API_MOCKING === "enabled"
? `${process.env.NEXT_PUBLIC_FE_URL}/api/v1/mock`
: `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/v1`;
4. Next.js 에서 MSW 사용 시 유의할 점
이전의 프로젝트(원데이 히어로)에서 MSW를 적용 못했던 것이 아쉬워 이번 프로젝트에서는 MSW를 적용할 수 있는 방법을 찾아봤습니다.
Next.js는 SSR과 CSR이 모두 사용되기 때문에 MSW를 Next.js에서 사용한다면 클라이언트 단과 서버 단에서 MSW가 모두 돌 수 있어야 합니다.
하지만 아직 서버 쪽에서 자연스럽게 MSW를 돌리는 방법이 나오지 않았기 때문에 node.js를 약간 이용해야 합니다.
// browser.ts
import { setupWorker } from 'msw/browser'
import { handlers } from './handlers'
const worker = setupWorker(...handlers)
export default worker
// http.ts
import { createMiddleware } from '@mswjs/http-middleware'
import cors from 'cors'
import express from 'express'
import { handlers } from './handlers'
const app = express()
const port = 9090 // 백엔드 서버 port 번호
app.use(
cors({
origin: 'http://localhost:3000', // 클라이언트 port 번호
optionsSuccessStatus: 200,
credentials: true,
})
)
app.use(express.json())
app.use(createMiddleware(...handlers))
app.listen(port, () => console.log(`Mock server is running on port: ${port}`))
5. Next.js 14에 MSW 2 도입하기
우리가 이제 주목해야 될 부분은 위에서 한번 언급됐던 handler.ts 파일( src/app/mocks/handlers.ts )입니다.
공식 홈페이지의 예시를 먼저 봅시다.
import { http, HttpResponse } from 'msw'
http.get(
// The "/pets" string is a path predicate.
// Only the GET requests whose path matches
// the "/pets" string will be intercepted.
'/pets',
// The function below is a "resolver" function.
// It accepts a bunch of information about the
// intercepted request, and decides how to handle it.
({ request, params, cookies }) => {
return HttpResponse.json(['Tom', 'Jerry', 'Spike'])
},
)
http namespace는 http.get(), http.post() 등의 메서드를 통해 http method를 나타낼 수 있습니다.
http는 첫 번째 인자로 predicate를, 두 번째 인자로 resolver를 받습니다.
첫 번째 인자 predicate 에는 요청의 path가, 두 번째 인자 resolver는 가로챈 (위의 예시 기준) 해당 경로의( '/pets' ) get 요청을 어떻게 해줄지에 대한 함수가 들어가게 됩니다.
다음으로 src/app/_component 위치에 아래와 같은 MSWComponent.tsx 파일을 만듭니다.
현재 코드가 브라우저 환경에서 실행되고 있고, API 모킹이 활성화되어 있다면 require를 사용하여 browser 모듈을 가져오게 된다는 내용을 담고 있습니다.
'use client'
import { useEffect } from 'react'
export const MSWComponent = () => {
useEffect(() => {
if (typeof window !== 'undefined') {
if (process.env.NEXT_PUBLIC_API_MOCKING === 'enabled') {
require('@/mocks/browser')
}
}
}, [])
return null
}
// layout.tsx
export default function RootLayout({ children }: Props) {
return (
<html lang='en'>
<body className={inter.className}>
<MSWComponent />
{children}
</body>
</html>
)
}
👏 👏 👏
정리하면 클라이언트 환경(브라우저)에서는 mockServiceWorker.js 가 요청을 가로채서 http 서버(http.ts)로 보내고 http 서버가 다시 handler.ts 파일을 실행시켜 해당 요청에 대한 mocking 처리를 하게 됩니다.
6. MSW 사용 예제 (+ Server Actions)
저번에 회원가입 form과 로그인 form의 데이터를 Server Actions를 이용해서 submit 받은 것까지 확인했습니다.
[Next.js 14 / 🔥 Tadak Tadak 🔥] Next.js 14 새로운 기능! Server Actions 알아보자!
🍎 목차 1. Server Actions 등장 2. Server Component 에서 Server Actions 사용하기 2. Client component에서 Server Actions 사용하기 1. Server Actions 등장 Server Actions are asynchronous functions that are executed on the server. They can b
dev-ea-jung.tistory.com
이렇게 받은 데이터를 이제 백엔드 서버에 보내서 DB에 저장해야 하는데 아직 백엔드 서버가 완성되지 않았으니 MSW를 이용해서 확인하고 이미 회원가입한 유저가 재 회원가입하는 상황에 대한 에러 상황도 만들어서 확인해 볼게요.
우리가 이제 다뤄야 할 부분은 다시 mocks 폴더의 handlers.ts 파일입니다.
// handler.ts
import { http, HttpResponse } from 'msw'
// 백엔드 server를 대체하는 부분이다.
export const handlers = [
http.post('/api/users', async () => {
// 이미 회원가입한 유자 회원가입으르 재시도 했을 경우 403 에러를 보내준다. ( 403는 백엔드와 상의해야 한다.) )
// return HttpResponse.text(JSON.stringify('user_exists'), {
// status: 403,
// })
// 회원가입 성공하는 경우
return HttpResponse.text(JSON.stringify('ok'), {
headers: {
'Set-Cookie': 'connect.sid=msw-cookie;HttpOnly;Path=/;Max-Age=0',
},
})
}),
]
오류 상황에 대한 코드는 우선 주석 처리해 뒀습니다.
const formAction = async (formData: FormData) => {
'use server'
let shouldRedirect = false
try {
const response = await fetch(
`${process.env.NEXT_PUBLIC_BASE_URL}/api/users`,
{
method: 'post',
body: formData,
credentials: 'include', // 쿠키가 전달 역할. ex) 회원가입한 유저가 재 회원가입을 시도한 경우를 처리 할 수 있다.
}
)
if (response.status === 403) {
return { message: 'user_exists' }
}
shouldRedirect = true
} catch (error) {
console.log(error)
return
}
// 로그인에 성공한 경우 home page로 redirect 시킨다.
if (shouldRedirect) {
redirect('/home') // redirect는 try/catch문 안에서 쓰면 안된다.
}
}
...
<form action={formAction}>
... nickname, id, password, profile image 에 대한 input
</form>
회원가입을 시도해 봅시다!
회원가입 form을 입력하고 🙌 회원가입 하기 🙌 제출을 버튼을 누르면 회원가입에 성공해 홈페이지로 redirect 하고, 입력한 form 정보도 네트워크 탭 Payload에서 잘 넘어온 것을 확인할 수 있습니다.
이미 회원가입한 유저가 재 회원가입하는 상황에 대한 에러 상황도 확인해 볼게요!
위의 handler.ts 파일의 403 error 상황의 주석을 풀고 회원가입 처리 코드를 반대로 주석처리를 해줍니다.
다시 회원가입을 시도해 볼게요.
회원가입에 실패해 홈페이지로 넘어가지 못하고 네트워크 탭의 Response에 { message: "user_exists" } 를 확인할 수 있습니다!
7. 마무리
백엔드 서버가 만들어질 때까지는 이런 식으로 MSW로 GET, POST 등의 상황을 처리할 것 같습니다.
위의 예시는 form 데이터의 post 상황이지만, GET Request의 상황이라면 실제 데이터의 형태대로 mock 데이터를 만들어 보내주는 식으로 사용할 것 같네요.
이렇게 MSW를 사용함으로써 백엔드 API 부재 상황을 대처하고 에러와 같은 예외성 상황에 대한 확인을 더 잘할 수 있을 것 같습니다.