はじめに
最近、MSWというモックサーバーがあることを知り、軽く触ってみたところ、使えそうだなと思ったのと同時にPOSTで登録したデータなど次回起動以降も使いまわせたら便利そうだなと思いました。
今回はMSWでデータを永続化する方法について紹介します。
前提
MSWについてはある程度の理解があることを前提とします。
下記あたりの記事をご覧いただければ、どんなことができるかわかると思います。
実装
永続化の実装としてはこちらのリポジトリのsrc/test/server/db.ts
から拝借します。
非常にコードがわかりやすいので特に解説はしないですが、一応、コメントだけつけています。
※簡略化のため、models
は必要最低限としています。
import { factory, primaryKey } from '@mswjs/data'
const models = {
user: {
id: primaryKey(Number),
name: String,
email: String,
},
}
export const db = factory(models)
export type Model = keyof typeof db
// localStorageからデータ取得
export const loadDb = () =>
Object.assign(JSON.parse(window.localStorage.getItem('msw-db') || '{}'))
// localStorageへデータ保存
export const persistDb = (model: Model) => {
const data = loadDb()
// @ts-ignore
data[model] = db[model].getAll()
window.localStorage.setItem('msw-db', JSON.stringify(data))
}
// モックDBの初期化
export const initializeDb = () => {
const database = loadDb()
// DBの復元
Object.entries(db).forEach(([key, model]) => {
const dataEntres = database[key]
if (dataEntres) {
dataEntres?.forEach((entry: Record<string, any>) => {
model.create(entry)
})
}
})
}
// localStorageをリセット
export const resetDb = () => {
window.localStorage.clear()
}
initializeDb()
モック用API
データ登録するための処理を作ります。
import { rest } from 'msw'
import { db, persistDb } from '@/mocks/db'
export const usersHandlers = [
// getについては省略
rest.post('/api/users', async (req, res, ctx) => {
const data = await req.json()
const id = db.user.count() + 1
const result = db.user.create({
...data,
id,
})
// データを追加したので保存(PATCHやDELETEも同様)
persistDb('user')
return res(
ctx.status(200),
ctx.json(result),
)
}),
]
ディレクトリ構造
最終的にMSWに関するディレクトリ構造はこのような感じになりました。
src
└── mocks
├── handlers
│ └── index.ts
│ └── users.ts
├── db.ts
└── browser.ts
APIを叩いてみる
ここまで準備できたら、実際にモック用のAPIを叩いてみます。
MSWだからといって特に意識することなく、通常通りのコードを書けばOKです。
import { useEffect, useState } from 'react'
import { Button, Container, Flex, Input, List, MantineProvider } from '@mantine/core'
import { useForm } from '@mantine/form'
import { resetDb } from '@/mocks/db'
type User = {
id: number
name: string
email: string
}
type Form = {
name: string
email: string
}
function App() {
const [users, setUsers] = useState<User[]>([])
const form = useForm({
initialValues: {
name: '',
email: '',
},
})
// user一覧取得
const fetchData = async() => {
const data = await fetch('/api/users').then((res) => res.json())
setUsers(data)
}
// user情報登録
const handleSubmit = async(values: Form) => {
const data = await fetch('/api/users', {
method: 'POST',
body: JSON.stringify(values),
}).then((res) => res.json())
setUsers([...users, data])
form.reset()
}
useEffect(() => {
fetchData()
}, [])
return (
<MantineProvider withGlobalStyles withNormalizeCSS>
<Container>
<form onSubmit={form.onSubmit(handleSubmit)}>
<Flex my="md" gap={5}>
<Input placeholder="UserName" {...form.getInputProps('name')} />
<Input placeholder="Email" {...form.getInputProps('email')} />
<Button type="submit">Send</Button>
<Button color="red" onClick={() => { resetDb() }}>Reset</Button>
</Flex>
</form>
{!users.length && (
<p>user情報がありません</p>
)}
<List>
{users.map(user => (
<List.Item key={user.id}>
{user.name}({user.email})
</List.Item>
))}
</List>
</Container>
</MantineProvider>
)
}
export default App
データ追加
POSTすると、localStorageに値が追加されていることが確認できます。
画面更新・再起動
ブラウザをリロードしたり、アプリケーションを再起動してもlocalStorageに保持されているデータを取得できていることが確認できます。
注意事項
この記事を読んでいる読者層的には今更なことかもしれませんが、下記の点に注意が必要です。
-
localStorageは同じドメイン(今回ローカル環境なのでport番号も含む)で共有されるため、値を上書きされないよう注意
- プロジェクトごとにkeyを変更する(今回の場合
msw-db
になっています)- ただし、localStorageには容量制限があるため、同じドメインにデータを多くは保持できない
- そもそもプロジェクトごとにport番号を変えてしまう
- プロジェクトごとにkeyを変更する(今回の場合
-
ブラウザを変えたら値は共有されない
- どうしてもデータを引き継ぎたいのであれば、開発者ツールからコピペなどでデータを移しましょう
参考
最後に
GoQSystemでは一緒に働いてくれる仲間を募集中です!
ご興味がある方は以下リンクよりご確認ください。