## 概要
[[useFetch]]を想定するユースケースとは異なるケースで利用した場合にどのような影響があるかの調査。具体的には [[$fetch]] のように単独のリクエストをするために使った場合にどうなるか。
## 環境
| 対象 | バージョン |
| -------------- | ---------- |
| [[macOS]] | 15.5 |
| [[Mockoon]] | 9.3.0 |
| [[Nuxt]] | 3.18.1 |
| [[Vue]] | 3.5.18 |
| [[Vue Router]] | 4.5.1 |
| [[TypeScript]] | 5.9.2 |
## モックサーバーの準備
### [[Mockoon]]のインストール
```console
brew install mockoon --cask
```
### モックサーバーの設定
POSTボディの `.name` をレスポンスに流し込む簡易的なモックサーバーを構築。
![[2025-08-09-15-03-21.avif]]
> [!code]- mockoon-sandbox.json
> ```json
> {
> "uuid": "fc75a3f2-ca34-4a24-b34b-be6139d03b9f",
> "lastMigration": 33,
> "name": "Mockoon sandbox",
> "endpointPrefix": "",
> "latency": 0,
> "port": 3333,
> "hostname": "",
> "folders": [],
> "routes": [
> {
> "uuid": "edf50ec4-36e2-4b44-9daf-5669567a4c2a",
> "type": "http",
> "documentation": "",
> "method": "post",
> "endpoint": "user",
> "responses": [
> {
> "uuid": "cc676992-d057-47d0-8d77-246f9383f704",
> "body": "{\n \"name\": \"{{body 'name'}}\"\n}",
> "latency": 0,
> "statusCode": 200,
> "label": "",
> "headers": [],
> "bodyType": "INLINE",
> "filePath": "",
> "databucketID": "",
> "sendFileAsBody": false,
> "rules": [],
> "rulesOperator": "OR",
> "disableTemplating": false,
> "fallbackTo404": false,
> "default": true,
> "crudKey": "id",
> "callbacks": []
> }
> ],
> "responseMode": null,
> "streamingMode": null,
> "streamingInterval": 0
> }
> ],
> "rootChildren": [
> {
> "type": "route",
> "uuid": "edf50ec4-36e2-4b44-9daf-5669567a4c2a"
> }
> ],
> "proxyMode": false,
> "proxyHost": "",
> "proxyRemovePrefix": false,
> "tlsOptions": {
> "enabled": false,
> "type": "CERT",
> "pfxPath": "",
> "certPath": "",
> "keyPath": "",
> "caPath": "",
> "passphrase": ""
> },
> "cors": true,
> "headers": [
> {
> "key": "Content-Type",
> "value": "application/json"
> },
> {
> "key": "Access-Control-Allow-Origin",
> "value": "*"
> },
> {
> "key": "Access-Control-Allow-Methods",
> "value": "GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS"
> },
> {
> "key": "Access-Control-Allow-Headers",
> "value": "Content-Type, Origin, Accept, Authorization, Content-Length, X-Requested-With"
> }
> ],
> "proxyReqHeaders": [
> {
> "key": "",
> "value": ""
> }
> ],
> "proxyResHeaders": [
> {
> "key": "",
> "value": ""
> }
> ],
> "data": [],
> "callbacks": []
> }
> ```
### 動作確認
```console
$ curl -X POST "localhost:3333/user" --json '{"name": "hoge"}'
{
"name": "hoge"
}
```
## Nuxt3の環境構築
### 初期設定
```bash
toki nuxt3 nuxt-sandbox
# official modulesは使用しない
```
起動確認。
```console
cd nuxt-sandbox
pnpm dev -o
```
### useFetchを使ったコードを書く
`app.vue`
```ts
<script setup lang="ts">
// 実際は別ファイルに定義されている想定
type Response = { name: string };
class Client {
async fetchUser(name: string): Promise<Response> {
const { data } = await useFetch(`/user`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
baseURL: "http://localhost:3333",
body: {
name,
},
});
return data.value as Response;
}
}
const client = new Client();
// ここからが通常のvueファイルフロー
const name = ref("");
const result = ref("");
watch(name, async () => {
const user = await client.fetchUser(name.value);
result.value = user.name;
});
</script>
<template>
<div>
<input type="text" v-model="name" placeholder="Enter your name" />
<h3>{{ result }}</h3>
</div>
</template>
```
モックサーバーに1秒のdelayを入れ、1秒以内に複数リクエストを飛ばしてみたが事象は再現しなかった。
そもそもvueファイルに定義された場合は、今回のような問題を避けるためにkeyが衝突しない構造になっている気がする。
## Nuxtプラグイン化する
プラグインにしただけでは変化がなかった。
`plugins/bff.ts`
```ts
type Response = { name: string };
class Client {
async fetchUser(name: string): Promise<Response> {
const { data } = await useFetch(`/user`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
baseURL: "http://localhost:3333",
body: {
name,
},
});
return data.value as Response;
}
}
export default defineNuxtPlugin((nuxtApp) => {
const bff = new Client();
return { provide: { bff } };
});
```
`app.vue`
```ts
<script setup lang="ts">
const name = ref("");
const result = ref("");
const { $bff } = useNuxtApp();
watch(name, async () => {
const user = await $bff.fetchUser(name.value);
result.value = user.name;
});
</script>
<template>
<div>
<input type="text" v-model="name" placeholder="Enter your name" />
<h3>{{ result }}</h3>
</div>
</template>
```
## 並列処理にしてみる
再現しない。
```ts
<script setup lang="ts">
const name = ref("");
const result = ref("");
const { $bff } = useNuxtApp();
async function concurrentFetchUser() {
const users = await Promise.all([
$bff.fetchUser(name.value + "1"),
$bff.fetchUser(name.value + "2"),
$bff.fetchUser(name.value + "3"),
]);
result.value = users.map((user) => user.name).join(", ");
}
watch(name, async () => {
await concurrentFetchUser();
});
</script>
<template>
<div>
<input type="text" v-model="name" placeholder="Enter your name" />
<h3>{{ result }}</h3>
</div>
</template>
```
[[Mockoon]]で[[ランダムにレイテンシーを設定 (Mockoon)|ランダムにレイテンシーを設定]]してみても変わらなかった。
これ以上の調査は難しそう。。