category icon
2023-12-08
JavaScript - Nuxt3

nuxt3 の ServerAPI で Cloudflare Workers KV を使う方法

nuxt
3.8.2
wrangler
3.19.0
cloudflare
profile
hikaru
Software Developer / DIY'er

nuxt3 の ServerAPI で Cloudflare Workers KV を扱うためのメモ。

shell
# nuxt3プロジェクトをセットアップ
$ npx nuxi init example-cloudflare-kv
$ cd example-cloudflare-kv
$ npm i

# パッケージを追加
$ npm i -D @cloudflare/workers-types wrangler

# Cloudflareにログインする
$ npx wrangler login

依存関係

package.json
{
  < 省略 >

  "devDependencies": {
    "@cloudflare/workers-types": "^4.20231121.0",
    "@nuxt/devtools": "latest",
    "nuxt": "^3.8.2",
    "vue": "^3.3.10",
    "vue-router": "^4.2.5",
    "wrangler": "^3.19.0"
  }
}

Cloudflare の Workers と KV の設定

wrangler.toml
name = "example-cloudflare-kv"
compatibility_date = "2023-12-07"
account_id = "<Cloudflare アカウントID>"
type = "javascript"
main = "./.output/server/index.mjs"
workers_dev = true

[site]
bucket = ".output/public"

[vars]
CLOUDFLARE_ENV = "production"

[build]
command = ""
upload.format = "service-worker"

[[kv_namespaces]]
binding = "STORAGE"
id = "<名前空間ID>"
preview_id = "<名前空間 ID>"

nuxt3のビルド先設定とStorage設定

nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    preset: `cloudflare`,

    // storage: Production
    storage: {
      db: {
        driver: `cloudflare-kv-binding`,
        binding: `STORAGE`,
      },
    },

    // storage: Development
    devStorage: {
      db: {
        driver: `memory`,
      },
    },
  },
});

アプリケーション部分

app.vue
<template>
  <p>{{ countRef.clicks }}</p>
  <button id="app-count-button" class="btn btn-border" @click="onClick">
    POST
  </button>
</template>

<script setup lang="ts">
import { Count } from "./server/api/count.patch";

const countRef = ref<Count>();

onMounted(async () => {
  // api: count取得
  const { data } = await useFetch<Count>("/api/count");

  countRef.value = {
    clicks: data.value?.clicks || 0,
  };
});

async function onClick() {
  // api: countのインクリメント
  const { data: count } = await useFetch<Count>("/api/count", {
    method: `PATCH`,
    body: { field: "CLICKS", operator: "INCREMENT" },
  });

  if (countRef.value && count.value) {
    countRef.value.clicks = count.value.clicks;
  }
}
</script>

/api/count API の GET メソッド

server/api/count.get.ts
export default defineEventHandler(async (event) => {
  // storage: データを取得
  const count = await useStorage().getItem(`db:count`);

  // api: countを返す
  return count;
});

/api/count API の PATCH メソッド

server/api/count.patch.ts
export interface Count {
  clicks: number;
}

export interface CountUp {
  field: "CLICKS";
  operator: "INCREMENT";
}

export default defineEventHandler(async (event) => {
  // api: パラメータを取得
  const body: CountUp = JSON.parse((await readRawBody(event)) as string);

  // storage: データを取得
  const count: Count = (await useStorage().getItem(`db:count`)) ?? {
    clicks: 0,
  };

  // インクリメント指示の判定
  if (body.field === `CLICKS` && body.operator === `INCREMENT`) {
    // クリック数をカウントアップ
    count.clicks++;

    // storage: データを保存
    await useStorage().setItem(`db:count`, JSON.stringify(count));
  }

  // api: countを返す
  return count;
});

nuxt3のビルドとデプロイ

shell
# nuxt3のビルド
$ nuxt build

# cloudflare worksにdeploy
$ wrangler publish