コンテンツにスキップ

Claude CodeでD1を使って診断アンケート・ランキングゲーム・役割分担表を作る

D1でデータ共有型Webアプリを作るで、匿名一行掲示板を作って公開した。この記事では同じ流れで別のものを3つ作る。診断つきのアンケート、ランキング付きのミニゲーム、当日の役割分担表。

3つのアプリの画面を横に並べたもの。左が選択式の質問が並ぶ診断アンケート、中央がスタートボタンとランキングのあるクリックバトル、右が役割を取る・外すボタンが並ぶ役割分担表

左から診断アンケート、ランキングゲーム、役割分担表。この画像は一人完結型の演習で作るコラージュアプリで並べた

作る手順は書かない。掲示板と同じだから。書くのは、何を作るかの仕様と、作っている途中で出てくる判断。同じ人をどう見分けるか、集計をどうやって見るか、不正をどこまで防ぐか、同時に押されたらどうするか。こういう判断は、掲示板のときは出てこなかった。

前提は、掲示板のハンズオンを最後まで終えていること。作業フォルダの作り方、D1 の作り方、公開のしかたは、あちらの記事で一度やっている。

変えるのは、CLAUDE.md に書く仕様だけ。 掲示板の仕様の代わりに、この記事の仕様を書く。準備から公開までの依頼文は、掲示板のときと同じものを記事末の補足に並べてあるので、あちらの記事を開き直さなくても進められる。

同じ人の見分け方も、掲示板と同じ。掲示板の仕様には「同じブラウザから投稿した人には毎回同じ会員番号」と書いた。エージェントはこれを、ブラウザに保存した識別用の文字列で実現している。名前も登録も要らない代わりに、別のブラウザや別の端末で開くと別人になるし、ブラウザの保存領域が消えても別人になる。この記事の3つも同じ方式でよい。一度きりの診断や遊びなら、それでよい。端末をまたいで同じ人だと分かる必要が出てきたら、その先は認証の話になる(→ 4章)。

選択式の質問が4問と、自由記述が1問。送ると、その場で「あなたは◯◯型」と出る。

回答は1件ずつ残る。 あとから取り出して数えたり、自由記述を読み返したりできる。同じブラウザからは一人一回答で、もう一度開くと前回の回答が入っていて、送り直すと上書きされる。

集計を見る画面は作らない。 理由は 1-3 で書く。

レベル診断アンケートの画面。選択式の質問が4問と自由記述が1問並び、下に「診断する」ボタンがある

4問に答えて自由記述を書き、診断する を押すとその場で区分が出る

仕様を CLAUDE.md に書いてもらう。

Claude
これから作るアプリの仕様を CLAUDE.md に書いてください。
## 作るもの:レベル診断アンケート
- 選択式の質問が4問と、自由記述が1問ある(質問は survey.md にまとめる)
- 選択式は選択肢ごとに点数(0〜2点)を持ち、合計点で3つの区分に分ける。0〜2点は「これから型」、3〜5点は「慣れてきた型」、6〜8点は「自走型」
- 送信すると、その場で区分の名前と説明を表示する
- 回答は1件ずつデータベースに保存する(各問の答え・自由記述・区分・送信日時)
- 同じブラウザからは一人一回答。もう一度開くと前回の回答が入っていて、送り直すと上書きされる
- 区分の判定はサーバー側で行う
- 集計ページは作らない

質問は別ファイルにしておく。質問は回ごとに変わるが、アプリの作りは変わらないから。

Claude
survey.md を作って。中身はこれ。選択肢は経験が浅い順に並んでいて、上から 0点・1点・2点。
# あなたのレベル診断
## ターミナル(黒い画面)を使ったことは?
- 使ったことがない
- コピペして実行したことがある
- 自分でコマンドを打って操作できる
## Git や GitHub は?
- 名前を聞いたことがある程度
- コミットやプッシュをしたことがある
- ブランチを分けて作業できる
## 作ったものを公開したことは?
- まだない
- サービスの画面から公開した
- コマンドでデプロイした
## エラーが出たときは?
- どうしていいか分からなくなる
- エラー文を検索したり AI に聞く
- ログを読んで原因を絞り込める
## いま作ってみたいものがあれば教えてください(自由記述)

仕様の「区分の判定はサーバー側で行う」の1行は、書かなくても動く。書いたのは、回答だけ残しておけば区分をあとから計算し直せるようにするため。区分の境目を変えたくなったとき、保存してある回答から全員分を再計算できる。画面の中で判定して結果だけ送る作りだと、それができない。

あとは D1 を作り、スキーマを作り、API と画面を作り、公開する。

集計ページを作らなかったのは、見るのが自分だけだから。

エージェントは wrangler d1 execute で本番のデータベースを直接操作できる。集計もこれで行う。

Claude
本番の D1 から回答を全部取り出して、区分ごとの人数と、質問ごとの選択肢の分布をまとめて。
自由記述は全部読んで、よく出てくる話題を3つくらいに要約して。

エージェントが本番のデータベースに SELECT を投げ、返ってきた結果を読んで表にする。自由記述もそのまま読ませられるのがこのやり方の強み。数えられないものを画面に出そうとすると一覧を作るしかないが、エージェントなら「要約して」と頼めばよい。

見たいものに合わせて、頼み方を変える。

Claude
「これから型」の人の自由記述だけ抜き出して
Claude
昨日以降に送られた回答だけで、区分ごとの人数を出して

画面を作ると、作った時点の見方に固定される。エージェントに頼む形なら、見たいものが変わるたびに頼み直せばよい。

1回目が失敗することがある。 本番の D1 への問い合わせは、ときどき認証まわりのエラーで返ってこないことがある。同じ依頼をもう一度頼めば通る。

回答が何百件にもなったら、いったんファイルに書き出してから読ませる。「回答を全部 JSON ファイルに書き出して、それを読んで集計して」と頼めばよい。

他の人にも集計を見せたくなったら、そのときに画面を作る。ただし誰でも見られる場所に置くことになるので、鍵をかける話とセットになる(→ 4章)。

診断結果を、あとから開ける自分専用のページにすることもできる。/r/<ID> のような URL を発行し、そこを開くと自分の区分と回答が出る。SNS で結果を共有したいときに使う形。

作るなら、次のことを頭に入れておく。

  • URL を知っている人は誰でも開ける。 本人確認をしていないので、URL を渡した相手にも、URL を推測した相手にも見える。ID を連番にすると、他人の結果を端から順に見て回れる
  • 自由記述に名前を書く人がいる。 仕組みでは防げない。結果ページを作るなら、自由記述は載せないか、設問文で「個人が分かることは書かないで」と断る

本人だけが開ける結果ページにしたいなら、それは認証の仕事になる(→ 4章)。

なお、このアンケートはデータベースを使わなくても作れる。Cloudflare には起きたことを記録していくだけの置き場があり、そこに回答を貯めてあとから数える作り方がある(→ イベントログでアンケートを作る。質問も診断もこの記事とほぼ同じ)。準備が要らないぶん、1件ずつ取り出したり直したりはできず、3か月で消える。数えるだけならそちらのほうが軽いし、D1 を無料プランの上限(10個)まで作ってしまったときの逃げ道にもなる。

ボタンを10回連打するのに何秒かかったかを競うゲーム。結果はランキングに載る。

10連打タイムアタックの画面。大きなスタートボタンの下に、全体・今週・今月で切り替わるランキング表がある

連打の秒数がランキングに載る。全体 / 今週 / 今月 の切り替えは 2-4 で足すもの

Claude
これから作るアプリの仕様を CLAUDE.md に書いてください。
## 作るもの:クリックバトル
- マウスかタッチパッドでボタンを10回連打するのに何秒かかるかを競うゲーム
- 最初のボタンを押してから、10回目のボタンを押した時まで何秒かかるか
- 結果(ミリ秒)はランキングとして記録される
- 初回ランキング登録には名前を入れる(同じブラウザからのアクセスは同じユーザとなる)
- それ以降は、自己ベストを更新しない限りランキング登録しない
- 記録には送信日時を入れる

このゲームは、ブラウザで測った秒数をサーバーに送る作り。サーバーはその数字を信じてランキングに載せる。

ブラウザから来る数字は、送る側が自由に書き換えられる。開発者ツールを開けば、1ミリ秒という記録を送れる。これを完全に防ぐ方法は無い。 サーバー側で時間を測っても、通信の遅れが加わるので正確な記録にならないし、10回のクリックを1回ずつサーバーに送らせる作りにしても、その通信を偽装できる。

だから、どこまでやるかを決める。個人の遊びなら、こんな線引きでよい。

  • ありえない値は弾く。 10回のクリックが 300 ミリ秒を切ることは人間にはできない。サーバー側でそれより速い記録を捨てる
  • 連投は受け付けない。 同じ人からの登録は1分に1回まで

これ以上の対策は、遊びの規模には見合わない。本気で不正する人が出たら、そのときは「◯◯の記録を消して」とエージェントに頼めばよい。

Claude
ありえない記録を弾くようにして。10回のクリックが 300 ミリ秒未満の記録は保存しない。同じ人からの登録は1分に1回までにして

どこまでやるかを決めるときの考え方は、安全なWebアプリのためににまとめてある。

ランキングが全体で1本しかないと、しばらく経ったころには上位が動かなくなる。あとから来た人は、どんなに頑張っても名前が載らない。これは不正対策とは別の、設計の問題。

対策は、期間で区切ったランキングを併せて持つこと。

Claude
ランキングに「全体」「今週」「今月」の切り替えを足して。記録に送信日時が入っているので、それで絞り込んで

仕様に「記録には送信日時を入れる」と書いておいたのはこのため。日時さえ残っていれば、スキーマを変えずに、絞り込みを足すだけでよい。

オフ会や勉強会の当日、集まった人が全員スマホで同じページを開き、空いている役割から自分の担当を1つ選ぶ。受付、タイムキーパー、写真係、片付け。誰が何を担当しているかは全員に見える。持ち寄りの分担(誰が何を持ってくるか)にも同じ形で使える。

前の2つと違うのは、1つの役割を取れるのが1人だけなこと。掲示板もアンケートもランキングも、みんなが自分の分を足していくだけだった。ここでは取り合いが起きる。

役割分担表の画面。名前の入力欄と役割の一覧が並び、空いている役割には「取る」、自分の担当には「外す」ボタンが出ている

空いている役割にだけ 取る が出る。自分が担当している役割は 外す に変わる

Claude
これから作るアプリの仕様を CLAUDE.md に書いてください。
## 作るもの:役割分担表
- 役割の一覧がある(roles.md に書く)
- 名前を入れて、空いている役割を1つ選ぶと、自分の担当になる
- 誰がどの役割を担当しているかが全員に見える。担当のいない役割は「空き」と出る
- 1つの役割に担当は1人。すでに誰かが取った役割は選べない。同時に取ろうとしたら、後の人には「取られました」と出す
- 自分が取った役割は自分で外せる(同じブラウザからなら)
- 一覧はページを開き直せば最新になる(自動更新はしない)

役割の一覧は別ファイルにしておく。会ごとに変わるから。

Claude
roles.md を作って。中身はこれ。
# 今日の役割
- 受付
- タイムキーパー
- 写真係
- 会場の片付け
- 二次会の店探し

画面で「空き」に見えていても、押した瞬間にはもう取られていることがある。2人が同時に「写真係」を見て、同時に押す。画面側のチェックでは防げない。 2人の画面は、どちらも「空き」を見ているから。

防げるのはサーバー側、それもデータベースの中だけ。「1つの役割に担当は1人」という決まりをデータベースの制約として持たせておくと、後から来た登録はデータベースが拒む。サーバーはその失敗を受けて「取られました」を返し、画面は一覧を読み直す。この作りになるのは、仕様に「同時に取ろうとしたら」の1行を書いてあるから。

これは D1 を使うからできること。回答を足すだけならイベントログでも作れたが(→ 1-4)、「先に取った1人だけ」を決めるのはデータベースの仕事。

同じ形は、役割以外にも当てはまる。フリーアドレスのオフィスでその日の席を取る。番号を振った貸し出し用の PC を誰が使っているか管理する。どちらも「1つのものに1人」で、空きを選んで取るのは役割分担表と同じ。

ただし取り合いが出るかどうかは、取ってから記録するのか、記録して確保するのかで変わる。席に座ってから「ここに座った」と入力するなら、同時に押しても実害はない(席はもう決まっている)。先に入力して席を押さえ、それから向かうなら、同時に押した2人のどちらかを決めなければならない。3-3 の作りが要るのは後者。仕様を書くとき、自分の使い方がどちらなのかを先に決めておく。

3-4. 当日その場だから成り立つこと

Section titled “3-4. 当日その場だから成り立つこと”

この記事の3つは、どれも「同じブラウザなら同じ人」で済ませている。端末をまたぐと別人になるし、名前は自己申告。位置付けで「一度きりならそれでよい」と書いたが、役割分担表はそれがいちばんはっきり出る。

  • 端末をまたがない。 その場でスマホを1回開くだけ
  • 名前は目の前にいる本人が入れる。 なりすます動機が無い
  • 他人の担当を外せてしまっても、その場で分かる。 仕組みで防がなくても、声をかければよい

同じアプリを「事前に Web で募る」形にすると、これらは成り立たなくなる。翌日に別の端末で開いたら自分の担当が外せない、誰かが他人の名前で登録した、という話が出てくる。利用シーンを当日その場に固定しているから、認証なしで成り立っている。 事前募集にするなら、4章の認証の話になる。

補足: 準備から公開までの依頼文

Section titled “補足: 準備から公開までの依頼文”

掲示板のときと同じ流れを、依頼文だけ抜き出して並べた。上から順に進めれば公開まで行ける。各手順の意味はD1でデータ共有型Webアプリを作るの2章から9章にある。

ベース名を決める

作業フォルダ・D1 データベース・Pages プロジェクトに付ける名前のもとになる。掲示板のときとは別の名前にする(quiz-ytoclick-yto のように)。短く、英小文字と数字とハイフンだけ。そのまま公開 URL(https://<ベース名>.pages.dev)になる。

作業フォルダを作り、Claude Code を起動する

Finder で ~/claude の中にベース名のフォルダを作る。Claudeデスクトップアプリで Code新規 → そのフォルダを指定。モードは 編集を受け入れる にしておく。

ベース名を覚えさせる

先頭の quiz-yto を自分のベース名に置き換えて渡す。

Claude
このプロジェクトのベース名は quiz-yto です。これを CLAUDE.md に記録してください。
- アプリ名・プロジェクト名 = ベース名
- D1データベース名 = ベース名のうしろに -db を付けた名前
これ以降、私のプロンプトの <ベース名> はベース名に、<D1データベース> は「ベース名-db」に読み替えて作業してください。

仕様を覚えさせる

診断アンケートなら 1-2、ランキングゲームなら 2-2、役割分担表なら 3-2 の依頼文を渡す。

Wrangler のログインを確認する

Terminal window
npx wrangler whoami

アカウント名やメールアドレスが表示されれば OK。表示されなければWranglerハンズオンの1章を参照してインストール・ログインする。

D1 データベースを作る

Claude
D1データベース <D1データベース> を作って

スキーマとマイグレーションファイル

Claude
CLAUDE.md に書いた仕様のとおりに、このアプリに必要なデータベースのテーブル設計を提案してください。
Claude
このスキーマでマイグレーションファイルを作成してください。
ファイルは migrations/0001_init.sql に保存してください。

wrangler.jsonc

Claude
このプロジェクト用の wrangler.jsonc を作って。
Cloudflare Pages プロジェクトで、公開ディレクトリは public。
D1データベース <D1データベース> を binding 名 DB で使う。
マイグレーションは migrations フォルダ。
compatibility_date は UTC(協定世界時)での今日の日付にする。
database_id は d1 create で表示された値を入れる。

API

Claude
CLAUDE.md の仕様と、先ほど設計したスキーマを使って、Pages Functions で API を作って。
D1 の binding 名は DB。

画面

Claude
CLAUDE.md の仕様と、これまでのDB定義・API定義を参考に
フロントエンドを public/index.html として作成してください。
シンプルで読みやすいデザインにしてください。
スマートフォンでも使いやすいようにしてください。

ローカルで動作確認

Claude
本番DBではなくローカルのD1で動作確認したい。
ローカルのD1にマイグレーションを適用(migrations apply)してから、ローカルサーバーを起動して

公開

Claude
本番DBにマイグレーションを適用(migrations apply)してから、wrangler で Cloudflare に直接デプロイして

初回は Pages プロジェクトがまだ無いので、デプロイが一度失敗する。エージェントがプロジェクトを作ってからデプロイし直すので、そのまま進めてよい。作成の許可を聞かれたら y

GitHub に置いて Git連携で公開するなら、D1W の10章の手順で。