Next.jsにおけるモックサーバ
Next.js で使えそうなモックサーバを試してみた備忘録
実施環境
項目 | 詳細 |
---|---|
PC | MacBook Pro(14 インチ、2021)Apple M1 Pro |
OS | MacOS Monterey 12.3 |
Node.js | v16.14.2 |
Next.js | v12.1.4 |
概要
Next.js 開発でバックエンドが出来ていないケースで、モックサーバどうしようとなったので軽く比較
モックサーバ | 概要 |
---|---|
API Routes | Next.js に最初から入っている機能。/pages/api 配下を使う。 |
JSON server | 一般的なモックサーバ |
MSW | ブラウザリクエストをインターセプトして任意のレスポンスを返す |
結論
MSW 使おうかなと思います。
API Routes
Next.js に最初から入っている機能のため、何か追加等する必要なし。
/pages/api
配下だけはページではなく、API エンドポイントとして扱われる機能。
サーバサイドのみでバンドルされるので、クライアントサイドのサイズには関係ないらしいです。
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next'
type Data = {
name: string
}
export default function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
res.status(200).json({ name: 'John Doe' })
}
HTTP メソッドで出し分ける場合は、req.method
を使用する
export default function handler(req, res) {
if (req.method === 'POST') {
// Process a POST request
} else {
// Handle any other HTTP method
}
}
Next(SSR)でのサンプル
axios の baseUrl をローカルホストにする
import axios from 'axios';
export const customAxios = axios.create({
timeout: 1 * 60 * 1000,
baseURL: "http://localhost:3000/api",
});
slug は本番 API と揃えて設定
import { customAxios } from '@/libs/axios';
...
const Test: NextPage<Props> = ({data}) => {
return (
<div>
<p>{data.name}</p>
</div>
);
}
export const getServerSideProps: GetServerSideProps = async () => {
const res = await customAxios('/hello');
const { data } = await res;
// Pass data to the page via props
return { props: { data } }
}
API Routes まとめ
- 公式の想定用途
- 普通に API エンドポイントとして
- API Routes でプロキシして外部サービスの URL をマスキングする
- 環境変数を使用してセキュアに外部サービスにアクセスする
設定は簡単。というより Next.js であれば何もする必要がない。が、上記の通りモックサーバとしての使用を想定してなさそうなので、微妙かもしれない…。
環境ごとの出し分けは必要そう。
JSON server
まずは json-server を入れる
// 公式ではグローバルにインストールしていた
npm install -g json-server
// 私は一応プロジェクトにインストール
yarn add --dev json-server
レスポンスデータを用意
{
"posts": [
{ "id": 1, "title": "json-server", "author": "typicode" }
],
"comments": [
{ "id": 1, "body": "some comment", "postId": 1 }
],
"profile": { "name": "typicode" }
}
スクリプト追加。上記作成したファイルを指定する。
"scripts": {
...
"json-server": "json-server --watch db.json --port 4000"
},
yarn json-server で起動して使う
Next(SSR)でのサンプル
API routes とほぼ一緒。baseUrl のポートが変わるくらい。
axios の baseUrl をローカルホストにする
import axios from 'axios';
export const customAxios = axios.create({
timeout: 1 * 60 * 1000,
baseURL: "http://localhost:4000/api",
});
JSON server まとめ
API routes との違い
- 別プロセスでローカルサーバを立てる必要あり(そのため環境には依存しない)
- 別のサーバなので、特に環境等で出し分ける必要がない
- サクッと試したいときにはよさそう。
MSW
Mock Service Worker の略。
実際にローカルホストでモック用のサーバーを起動するのではなく、サービスワーカーレベルでリクエストをインターセプトしてリクエストを返却するらしいです。
インストール
npm install msw --save-dev
# or
yarn add msw --dev
どれをインターセプトするか指定するファイルの作成
src/mocks/handlers.js ファイルを作成して、インターセプトする URL、レスポンスを指定する。
import { rest } from 'msw'
export const handlers = [
rest.post('/login', (req, res, ctx) => {
// Persist user's authentication in the session
sessionStorage.setItem('is-authenticated', 'true')
return res(
// Respond with a 200 status code
ctx.status(200),
)
}),
rest.get('/user', (req, res, ctx) => {
// Check if the user is authenticated in this session
const isAuthenticated = sessionStorage.getItem('is-authenticated')
if (!isAuthenticated) {
// If not authenticated, respond with a 403 error
return res(
ctx.status(403),
ctx.json({
errorMessage: 'Not authorized',
}),
)
}
// If authenticated, return a mocked user details
return res(
ctx.status(200),
ctx.json({
username: 'admin',
}),
)
}),
]
クライアントサイドでインターセプトするための設定
下記を実行することで、mockServiceWorker.js
が作成される。
public/の部分は公開ディレクトリを指定する。
npx msw init public/ --save
src/mocks/browser.js を作成
// src/mocks/browser.js
import { setupWorker } from 'msw'
import { handlers } from './handlers'
// This configures a Service Worker with the given request handlers.
export const worker = setupWorker(...handlers)
Node 側でインターセプトするための設定
src/mocks/server.js ファイルを作成
// src/mocks/server.js
import { setupServer } from 'msw/node'
import { handlers } from './handlers'
// This configures a request mocking server with the given request handlers.
export const server = setupServer(...handlers)
アプリでインポート
アプリ側でインポートする。
その際サーバ側とクライアント側で出し分ける必要がある。(process.browser は deprecate になったらしく、typeof window での出し分けがいいとのことでした。)
開発環境のみで使用するものなので、環境変数でも出し分ける。
// src/pages/_app.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
if (process.env.NODE_ENV === 'development') {
if (typeof window === 'undefined') {
const { server } = require('../mocks/server')
server.listen()
} else {
const { worker } = require('../mocks/browser')
worker.start()
}
}
ReactDOM.render(<App />, document.getElementById('root'))
実際にリクエストする
import { useState } from 'react'
export default function Home({ book }) {
const [reviews, setReviews] = useState(null)
const handleGetReviews = () => {
// Client-side request are mocked by `mocks/browser.js`.
fetch('/reviews')
.then((res) => res.json())
.then(setReviews)
}
return (
<div>
<img src={book.imageUrl} alt={book.title} width="250" />
<h1>{book.title}</h1>
<p>{book.description}</p>
<button onClick={handleGetReviews}>Load reviews</button>
{reviews && (
<ul>
{reviews.map((review) => (
<li key={review.id}>
<p>{review.text}</p>
<p>{review.author}</p>
</li>
))}
</ul>
)}
</div>
)
}
export async function getServerSideProps() {
// Server-side requests are mocked by `mocks/server.js`.
const res = await fetch('https://my.backend/book')
const book = await res.json()
return {
props: {
book,
},
}
}
Example
いくつか環境ごとのサンプルがあるので、かなり参考になると思います。上記も Next.js のサンプルから持ってきたものです。
MSW まとめ
設定は他のものより少し苦労したしあまり理解出来ていないですが、一度設定してしまえばモックサーバを意識せずに開発できるのがよさそう。handlers.js で特定のエンドポイントのみを指定してインターセプトできるので、baseUrl を書き換えたりといったことをする必要がない。モックに向けるために本番コードに何か手を加えなくていいのはとてもいいと思いました。
結論:再
設定はちょっと大変ですが、やっぱり本来のエンドポイントを叩きにいけるというのは良いなと思いました。
MSW 使ってみようかと思います。
項目 | API Routes | JSON server | MSW |
---|---|---|---|
設定の簡単さ | ◯ | △ | ✕ |
環境によらない | - (Next の機能) | ◯ | △ |
本番コードに与える影響 | △ | △ | ◎ |
Discussion