Cloudflare KV で個人開発の最小キーバリューストアを作る手順

Cloudflare Workers で API が動き、D1 でデータも残せるようになると、次に出てくるのが「わざわざテーブルにするほどでもない、ちょっとした値」をどこに置くか、という悩みです。機能フラグ、APIのレスポンスキャッシュ、ログイン中のセッション、設定値 ── こういう「キーで引いて、たまに書き換える」データに SQL を立てるのは大げさです。

ここで向くのが Cloudflare KV。グローバルに分散したキーバリューストアで、Worker から env.KV.get("key") のように 1 行で読めます。世界中のエッジに値が複製されるので読み取りがとても速い反面、書き込みは「すぐ世界中に反映される」わけではない(後述の結果整合)という性質があります。本記事では、KV に最小の値を置いて Worker から読み書きするまでを、wrangler で通します。

Workers の最小デプロイは Workers で最小の API を公開する手順 で扱いました。本記事はその続編 ── **「API は動いた、次は軽量な値の置き場」**の地続きで読めます。

KV はいつ使うか(D1 / R2 との違い)

Cloudflare のデータストアは 3 系統。「何を置きたいか」で選びます。

KVD1R2
データモデルKey-Valueリレーショナル(SQLite)オブジェクトストレージ
向く用途設定・フラグ・キャッシュ・セッションテーブル・JOIN・集計画像・動画・大きいファイル
一貫性結果整合(書き込みが世界に伝播するまで最大 60 秒)強い強い
読み取りエッジにキャッシュされ非常に速い普通普通
個人開発の体感短命で読み中心の値の置き場SQL を書きたい時の第一選択静的アセットの倉庫

判断はシンプルです。**「キーで 1 個引きたい・読みが圧倒的に多い・少し古くても困らない」なら KV。「検索・JOIN・正確なカウント」が要るなら D1「ファイルそのもの」**なら R2。本記事は KV 単独の最小経路で進めます。

手順 1:wrangler kv namespace create で作る

Workers プロジェクト(my-api)の中で実行します。Workers をまだ用意していなければ、先に Workers の手順 を通してください。

cd my-api
npx wrangler kv namespace create my-kv

数秒で、wrangler.toml に貼るための TOML スニペットが返ってきます。

🌀 Creating namespace with title "my-api-my-kv"
✨ Success!
Add the following to your configuration file:
[[kv_namespaces]]
binding = "KV"
id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

binding = "KV" がコード側からの参照名(env.KV で呼ぶ)、id がネームスペースの識別子です。

手順 2:wrangler.toml に bindings を書く

返ってきたスニペットを wrangler.toml の末尾に貼ります。wrangler dev(ローカル実行)でも使うなら preview_id を足しておくと迷いません。

name = "my-api"
main = "src/index.ts"
compatibility_date = "2026-01-01"

[[kv_namespaces]]
binding = "KV"
id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
preview_id = "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"

id は Git に入っても問題ありません(ID 単体ではアクセスできず、Cloudflare のアカウント認証が必須なため)。preview_idnpx wrangler kv namespace create my-kv --preview で別途作れます。

手順 3:Worker から put / get / delete する

src/index.ts を書き換えます。

interface Env {
  KV: KVNamespace;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);
    const key = url.searchParams.get('key') ?? 'greeting';

    if (request.method === 'PUT') {
      const value = await request.text();
      // 1 時間で自動消滅させる(TTL は秒・最小 60)
      await env.KV.put(key, value, { expirationTtl: 3600 });
      return Response.json({ ok: true });
    }

    if (request.method === 'GET') {
      const value = await env.KV.get(key); // 無ければ null
      if (value === null) return new Response('Not Found', { status: 404 });
      return new Response(value);
    }

    if (request.method === 'DELETE') {
      await env.KV.delete(key);
      return Response.json({ ok: true });
    }

    return new Response('Method Not Allowed', { status: 405 });
  },
};

KV の API は 4 つ覚えれば足ります。

  • put(key, value, options?) … 書き込み。expirationTtl(秒・最小 60)や metadata を付けられる。
  • get(key, options?) … 読み込み。無いキーは例外でなく null{ type: "json" } を渡すと JSON を自動パースして返す。
  • delete(key) … 削除。
  • list({ prefix, limit, cursor }) … キー一覧。prefix で絞り、cursor でページング。

JSON を扱うときは型指定が楽です。

await env.KV.put('user:1', JSON.stringify({ name: 'alice', plan: 'free' }));
const user = await env.KV.get<{ name: string; plan: string }>('user:1', { type: 'json' });

手順 4:ローカルで動かす/CLI で値を入れる

wrangler dev を立てて curl で確認します。

npx wrangler dev
curl -X PUT 'http://localhost:8787/?key=greeting' -d 'こんにちは'
curl 'http://localhost:8787/?key=greeting'   # → こんにちは

コードを介さず、CLI から直接値を出し入れすることもできます(シードや確認に便利)。--local--remote でローカル/本番が分かれます。

# 本番のネームスペースに 1 件入れる
npx wrangler kv key put --binding=KV "feature:newui" "on" --remote

# 取り出す・一覧する
npx wrangler kv key get --binding=KV "feature:newui" --remote
npx wrangler kv key list --binding=KV --remote

本番へは npx wrangler deploy で 1 行。bindings は自動で繋がるので、コードを変えずに env.KV が本番のネームスペースを指します。

結果整合(KV でいちばん大事な性質)

KV は「速い読み取り」と引き換えに、書き込みが世界中のエッジに反映されるまで最大 60 秒ほどかかることがあります(結果整合)。同じ場所では自分の書き込みがすぐ読めても、別リージョンからのアクセスではしばらく古い値が返る、という状況が起こり得ます。

つまり、「書いた直後に、世界中で必ず最新が読めること」が要る用途には向きません。たとえば在庫の正確なカウント、課金トランザクション、連番採番のようなものは KV ではなく D1(や用途次第で Durable Objects)に寄せます。逆に、機能フラグ・設定・キャッシュ・セッションのように「数十秒古くても実害がない」データは、KV の独壇場です。

挫折ポイント

  • 結果整合を忘れてカウンタに使うget して +1 して put ──これは複数同時アクセスで壊れます(read-modify-write は KV の不得手)。正確な集計は D1、高頻度カウンタは Durable Objects。
  • 同じキーを秒間何度も書く … KV は同一キーへの高頻度書き込みに向きません。無料枠も書き込みは控えめ(1 日あたり 1,000 回程度)。書き込みが多い設計なら、そもそも KV 以外を検討する。
  • list を DB クエリ代わりに使うlist はページングされるキー列挙であって検索ではありません。「条件で絞って取りたい」が増えてきたら D1 へ。キー設計(user:1:profile のような prefix)で引きやすくしておく。
  • TTL を 60 秒未満にしようとするexpirationTtl の下限は 60 秒。「数秒で消したい」はできません。
  • 値のサイズ上限 … 1 値あたり最大 25MB・キーは最大 512 バイト。大きいファイルは KV でなく R2 へ。
  • --local--remote の取り違え … D1 と同じく、ローカルと本番でデータは別物。kv key put の対象を毎回確認する習慣を最初に作る。

まとめ:KV は「キーで引く・読み中心・少し古くてOK」

最小手順を振り返ります。

  1. npx wrangler kv namespace create my-kv
  2. 返ってきた TOML スニペットを wrangler.toml に貼る
  3. Worker で env.KV.put / get / delete / list
  4. wrangler dev で確認 →npx wrangler deploy

ここまでで、Workers + KV の最小構成が完成します。KV は万能 DB ではありませんが、**「読みが多くて、少し古くても困らない値」**に当てれば、SQL を立てるより速く・安く片づきます。迷ったら、正確さが要るものは D1、ファイルは R2、フロント配信は PagesPages Functions ── という棲み分けで考えると外しません。

まずは機能フラグを 1 つ、feature:newui = onkv key put するところから。


サーバーも DB も軽量ストアも動かせる。けれど 読まれない/登録に至らない ── その構造を WHY → HOW で 1 冊にまとめたのが、下記の本です。個人開発者・インディー Web 作者・動画作者向けに、LP・短文・メールの戦場別の書き方と、Claude を相棒にしたプロンプト連鎖の実演を含めて統合しています。

#個人開発#Cloudflare#リファレンス