StackBlitz上でモックサーバーを立ち上げてTanStack Query(React Query)を素振りしてみる
こんにちは!CastingONEの大沼です。
始めに
プログラミングの勉強をする際は実際に書いて学ぶのが良いと思っていますが、オンライン上で気軽に書けるとコードの共有&動作確認という観点で尚良いと思っています。僕も簡単なコードの実装はStackBlitzを愛用しています😊
StackBlitzではNodeの実行もできるためバックエンド側の実装を書くこともできるのが魅力です。これによってreact-queryのようなAPIとの通信が必要になるライブラリもモックサーバーを同時に立てることでStackBlitzのみで検証することができます!
そこでこの記事では具体例としてjson-serverとreact-queryを使ってStackBlitz上で実装する例を紹介したいと思います。
サンプルコード
先に今回作成したサンプルコードを以下に貼ります。単純に環境が欲しいだけの方やコードの詳細を見たい方はこちらをForkして色々いじって貰えればと思います😄
次のセクションからは具体的にどういう風に環境構築をしたか説明します。
json-serverとreact-queryの環境構築
まずReactの環境についてはテンプレートから選ぶと楽なのでReact TypeScriptのものを選択します。
json-serverのセットアップ
作成されたプロジェクトのターミナルに入り、json-serverをインストールします。
$ npm install -D json-server@^0
続いてモックデータ用のjsonファイルを用意します。とりあえずは以下のように空のdb.json
を用意するだけで良いです。
$ echo "{}" >> ./db.json
後はpackage.json
に起動タスクを書いたらモックサーバーの完成です。ここではせっかくモックにしているのでレスポンスに1秒間遅延を挟むようにしています。
{
"scripts": {
"dev:mock": "json-server --watch db.json --delay 1000 --port 6000"
}
}
このタスクが正常に動くかcurlを使って試してみたいと思います。まずはこのタスクを起動します。StackBlitzのpreview画面が勝手に動いてしまうとは思いますが、そちらは気にしないでください。
この状態で以下のcurlを叩くと実際にデータを作成することができると思います。空だったdb.json
もPOSTした内容が登録されていると思います。
$ curl -X POST -H "Content-Type: application/json" -d '{"id": 100, "text": "curlから登録"}' http://localhost:6000/todos
このデータをAPIから取得するには以下のcurlで確認できます。
$ curl http://localhost:6000/todos
[
{
"id": 100,
"text": "curlから登録"
}
]
なお、編集・削除は以下のcurlで実行することができ、モックサーバーとして機能していることが確認できると思います。
$ curl -X PUT -H "Content-Type: application/json" -d '{"id": 100, "text": "curlから編集"}' http://localhost:6000/todos/100
$ curl -X DELETE http://localhost:6000/todos/100
モックサーバーとフロントの開発サーバーを同時に立ち上げる
続いてモックサーバーとフロントの開発サーバーが同時に立ち上がるようにします。ターミナルを分割してそれぞれのタスクを実行しても問題はないですが、一つのタスクで同時に起動できた方が楽だと思うのでその設定を入れます。
タスクを同時に実行するためにここではnpm-run-allを使ったので、それをインストールします。
$ npm install -D npm-run-all
後はpackage.jsonに以下のようなタスクを書いたらnpm run dev
でモックサーバーとフロントの開発サーバーの両方が立ち上がるようになります。StackBlitzではdev
タスクが初回起動時に自動で実行されるようなので、StackBlitzでアクセスしたのと同時にこれらのタスクも立ち上がります。
{
"scripts": {
"dev": "npm-run-all -p dev:mock dev:front",
"dev:mock": "json-server --watch db.json --delay 1000 --port 6000",
"dev:front": "vite",
}
}
react-queryを使ってモックサーバーと通信する
最後にreact-queryをインストールしてモックサーバーと通信できるようにします。
$ npm install @tanstack/react-query
$ npm install -D @tanstack/react-query-devtools
main.tsxを以下のように追加します。
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
import './index.css';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
+const queryClient = new QueryClient();
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
+ <QueryClientProvider client={queryClient}>
<App />
+ <ReactQueryDevtools />
+ </QueryClientProvider>
</React.StrictMode>
);
後はそれぞれ以下のようなリクエストコードを定義してhooksを呼ぶことで通信できるようになります。なお、フロント側でユニークなIDを生成するにはcrypto.randomUUID
を使うのが楽だったためidはstring型で送るように変更しています。詳細のコードは最初に貼ったサンプルコードの方をご参照ください。
TODOの作成
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { Todo, getFetchTodosKey } from './fetchTodos';
export const createTodo = (newTodo: Todo) => {
return fetch('https://localhost:6000/todos', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(newTodo),
}).then((r) => r.json());
};
export const useMutationCreateTodo = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (newTodo: Todo) => createTodo(newTodo),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: getFetchTodosKey(),
});
},
});
};
TODOの取得
import { useQuery } from '@tanstack/react-query';
export type Todo = {
id: string;
text: string;
};
export const fetchTodos = () => {
return fetch('https://localhost:6000/todos').then(
(r): Promise<Todo[]> => r.json()
);
};
export const getFetchTodosKey = () => ['todos'];
export const useQueryTodos = () => {
return useQuery({
queryKey: getFetchTodosKey(),
queryFn: () => fetchTodos(),
refetchInterval: 10 * 1000,
});
};
TODOの編集
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { Todo, getFetchTodosKey } from './fetchTodos';
export const editTodo = (todoId: string, newTodo: Todo) => {
return fetch(`https://localhost:6000/todos/${todoId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(newTodo),
});
};
export const useMutationEditTodo = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (newTodo: Todo) => editTodo(newTodo.id, newTodo),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: getFetchTodosKey(),
});
},
});
};
TODOの削除
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { getFetchTodosKey } from './fetchTodos';
export const deleteTodo = (todoId: string) => {
return fetch(`https://localhost:6000/todos/${todoId}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
}).then((r) => r.json());
};
export const useMutationDeleteTodo = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (todoId: string) => deleteTodo(todoId),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: getFetchTodosKey(),
});
},
});
};
その他
StackBlitz上でdb.jsonを直接編集するとクラッシュする
npmのタスク上では --watch db.json
というオプションが付いているため直接ファイルを編集した場合json-serverが再起動されるのですが、これが上手くいかずタスクが強制終了されてしまいます。エラーを見た感じポートが衝突しているのが原因のようですが、おそらくStackBlitz上ではポートがすぐに解放されないためこのような現象が起きているのかなと思いました🤔 ローカルでは問題なく再起動できていました。
これを何とかするためには自前でjson-serverを立ち上げて再起動ではなくデータだけ更新することで解消できますが、わざわざ書いてまでやるほどなのかなという感じでした。一応以下のようなコードで動くことを確認でき、サンプルコードでもdev-manual:mock
というタスク名で起動するようにしているので興味がある方はご参照ください。
import jsonServer from "json-server";
import fs from "fs";
import path from "path";
import { isEqual } from "lodash-es";
let server: ReturnType<typeof jsonServer.create> | null = null;
let router: jsonServer.JsonServerRouter<object> | null = null;
const startServer = () => {
server = jsonServer.create();
router = jsonServer.router("./db.json");
const middlewares = jsonServer.defaults();
server.use(middlewares);
server.use((_req, _res, next) => {
setTimeout(() => {
next();
}, 1000);
});
server.use(router);
server.listen(6000, () => {
console.log("JSON Server is running");
});
};
fs.watch(
// ESModuleで起動している影響で__dirnameは使えないのでダイレクトに相対パスを指定する
path.resolve("./db.json"),
(_event, file) => {
if (file == null || router == null) {
return;
}
try {
const obj = JSON.parse(fs.readFileSync(file, { encoding: "utf8" }));
if (!isEqual(obj, router.db.getState())) {
console.log("db.json directly changed. reload db.json data.");
router.db.setState(obj);
}
} catch (e) {
console.error(e);
}
},
);
startServer();
終わりに
以上がStackBlitz上でモックサーバーを立ち上げてreact-queryを素振りする方法でした。今までAPI通信となると別なサービスを使ってAPIを用意したりしていましたが、無料枠だとずっと立ち上げ続けるわけにもいかずどうしても満足のいくサンプルを作ることができませんでした。それがStackBlitzは全てローカルのリソースを使うため無料枠でも自分用のモックサーバーを立ち上げることができ、オンライン上でAPI通信も含めた検証が非常にやりやすくなりました😄 react-queryなどAPIが絡むコードの検証をしたいときの参考になれれば幸いです。
弊社ではいっしょに働いてくれるエンジニアを募集中です。社員でもフリーランスでも、フルタイムでも短時間の副業でも大歓迎なので、気軽にご連絡ください!
Discussion