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 系統。「何を置きたいか」で選びます。
| KV | D1 | R2 | |
|---|---|---|---|
| データモデル | 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_id は npx 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」
最小手順を振り返ります。
npx wrangler kv namespace create my-kv- 返ってきた TOML スニペットを
wrangler.tomlに貼る - Worker で
env.KV.put / get / delete / list wrangler devで確認 →npx wrangler deploy
ここまでで、Workers + KV の最小構成が完成します。KV は万能 DB ではありませんが、**「読みが多くて、少し古くても困らない値」**に当てれば、SQL を立てるより速く・安く片づきます。迷ったら、正確さが要るものは D1、ファイルは R2、フロント配信は Pages と Pages Functions ── という棲み分けで考えると外しません。
まずは機能フラグを 1 つ、feature:newui = on を kv key put するところから。
サーバーも DB も軽量ストアも動かせる。けれど 読まれない/登録に至らない ── その構造を WHY → HOW で 1 冊にまとめたのが、下記の本です。個人開発者・インディー Web 作者・動画作者向けに、LP・短文・メールの戦場別の書き方と、Claude を相棒にしたプロンプト連鎖の実演を含めて統合しています。