こんにちは、AIシステムズです。
この記事は、代表コバが現場で蓄積してきた知見をもとに、AIを活用して構成・執筆し、弊社にて最終チェックを行ったものです。
弊社では、リアルイベントの参加者がスマホから相互に投票し、両思いだったペアだけにマッチング通知を出す「イベント型マッチング投票システム」を、Claude Code を主役にしてフルスクラッチで構築しました。婚活イベント・交流会・名刺交換会・社内ファン投票など、当日参加者同士のマッチングを必要とする現場に幅広く使える仕組みです。
本記事では、Node.js + MySQL を使ったこの種のシステムを、Claude Code でゼロから組み立てる手順を、実装単位で整理します。
- イベント運営者と参加者、2 系統のユーザーを扱うシステム設計
- ワンタイムURLでログイン状態を管理する認証フロー
- 投票結果から両思いペアだけ抽出するマッチングロジック
- Apache リバースプロキシ+PM2 による安定運用
目次
- このシステムをどう作ったか(全体像)
- システム要件と機能整理
- 技術スタックと全体構成
- 手順1:VPS と Node.js 環境の準備
- 手順2:データベース設計とテーブル作成
- 手順3:ワンタイムURL認証フローの実装
- 手順4:投票とマッチングロジック
- 手順5:メール送信と画像アップロード
- 手順6:管理画面の構築
- 手順7:Apache リバースプロキシと PM2 でデプロイ
- Claude Code に任せるときのコツ
このシステムをどう作ったか(全体像)
全体の進め方は次のとおりです。
- VPS に Node.js と MariaDB を導入
- Apache を「フロントの静的HTML配信 +
/api/*のリバースプロキシ」として設定 - サーバー上に Claude Code をインストールし、プロジェクトディレクトリで起動
- 要件を自然言語で渡し、テーブル定義 → ルーティング → フロント の順に作らせる
- PM2 で Node を常駐起動し、Apache の設定でドメイン直下にぶら下げる
特徴は、「フロントは静的HTML、ロジックは Node.js API、認証はセッション」というシンプルな三層構成で割り切ったことです。SPA フレームワークを使わなくても、必要な動的処理は API 側に寄せて十分にさばけます。
システム要件と機能整理
最初に Claude Code と合意した要件は、概ね次の内容です。
- 運営者がイベントを作成し、参加者を登録できる
- 参加者には ワンタイムURL をメールで送付し、URL からアクセスするだけでログイン状態になる
- 参加者は当日までにプロフィール(年代・趣味・自己PR・画像など)を入力
- イベント当日、運営が投票を解禁すると、参加者は異性の参加者に対して最大5名まで希望順位を付けて投票
- 投票が締まったタイミングで両思いだったペアだけにマッチング通知+連絡先を送信
- 運営は管理画面からイベント、参加者、投票、マッチング状況をリアルタイムで把握できる
要件はまずユースケースのストーリーとして書き出してから、機能箇条書きに落とし込みます。Claude Code はストーリーから機能を派生させるのが得意なので、最初に物語で渡したほうが齟齬が減ります。
技術スタックと全体構成
- Web サーバー:Apache(静的HTML配信 +
/api/*を Node.js にプロキシ) - アプリケーション:Node.js 20 + Express 4
- DB:MariaDB(MySQL 互換)
- セッション:
express-session+express-mysql-sessionで MySQL に保存 - セキュリティ:
helmet(CSPなどヘッダ付与)、csurf(CSRF トークン)、ハッシュ化トークン(SHA-256) - メール:
nodemailerで SMTP 送信、送信履歴は DB に記録 - 画像:
multerでアップロード、sharpでリサイズ&JPEG変換 - ログ:
winstonで構造化ログ、アプリ内で操作監査ログも別テーブルに保存 - プロセス管理:PM2(自動再起動・メモリ上限再起動)
SPA フレームワークを使っていないのは、「サイトの動的部分は限定的で、検索エンジン経由ではなく URL 直送で来訪する」という性質に最適化したためです。
手順1:VPS と Node.js 環境の準備
VPS の初期設定(OS 更新、SSH 公開鍵化、ファイアウォール、SSL)は前提として済ませてある状態で始めます。
- Node.js 20 を NodeSource または nvm で導入
- MariaDB を導入し、
matching_db用の専用ユーザーを作成 - プロジェクトディレクトリを作り、
npm init -yからexpress、mysql2、express-session、helmet、csurf、multer、sharp、nodemailer、winston等を導入 - PM2 をグローバルインストールし、
ecosystem.config.jsでアプリ起動定義を作成
ここから先は Claude Code に任せます。ターミナルでプロジェクト直下に移動し、claude を起動してから「ここまで入った状態。これから Express アプリを作る」と現状を伝えます。
手順2:データベース設計とテーブル作成
このシステムでは、以下のテーブルを設計しました。
- events:イベント本体(開催日時、投票表示フラグ、希望数)
- participants:参加者(氏名、年齢、メール、性別、当日番号、ステータス)
- profiles:公開プロフィール(表示名、職業、趣味、自己PR、画像パス、連絡先)
- tokens:ワンタイムURL用トークン(ハッシュ化して保存、再発行で旧トークンを無効化)
- codes:会場で配布する短いログインコード(運営から手渡し対応用)
- votes:投票記録(1〜5位の希望、または「希望なし」フラグ)
- matches:マッチング結果(両思いペアと、何位同士でマッチしたかの記録)
- likes / memos:参加者個人がメモ用に保存できる気になる相手
- contacts:マッチング後の連絡先送信ログ
- mail_logs / audit_logs:メール送信履歴と管理操作の監査ログ
- sessions:
express-mysql-session用
Claude Code に「上記テーブルの DDL を書いて」と渡せば、外部キー制約・インデックス・utf8mb4 指定まで含めた schema.sql を一気に生成してくれます。必ず外部キー制約とユニーク制約をレビューし、二重投票・二重マッチング・重複登録が起きない設計になっているかを人間が確認します。
手順3:ワンタイムURL認証フローの実装
イベント参加者はパスワードを覚える必要がなく、メールに届いた URL をタップするだけでログイン状態になる設計です。
- 運営が参加者を登録すると、サーバー側で 長いランダム文字列 を生成
- 生成した文字列は SHA-256 でハッシュ化して DB に保存(平文は保存しない)
- 生のトークンはメールの URL クエリ部分のみに含まれる
- 参加者が URL を踏むと、サーバーはハッシュ照合してセッションを発行
- 運営が「再発行」操作をすると、旧トークンの
is_activeを 0 にして無効化
このパターンは、個人情報を扱う短期イベントのログインとして現場で扱いやすく、参加者のドロップ率が大きく下がります。Claude Code には「crypto で 32 バイトのランダム生成→hex→SHA-256 ハッシュで DB 保存」と具体的に指示すると、ブレずに実装されます。
手順4:投票とマッチングロジック
投票画面では、参加者が異性のリストから最大 5 名まで希望順位を付けて送信します。
- 同一イベント内で 1 人 1 票(
UNIQUE(event_id, participant_id)) - 希望順位の重複はサーバー側で弾く(フロントだけのバリデーションに任せない)
- 「希望なし」を選んだ場合は
no_preference=1として記録、マッチング対象から除外
マッチング処理は SQL 一発に寄せると透明性が高く、結果検証も簡単になります。
- 女性が男性を希望順位で投票したレコードを取得
- 男性が女性を希望順位で投票したレコードを取得
- 両者の希望順位を相互参照し、両方に名前があるペアだけを抽出
- 2 つの希望順位の合計が小さい順に並べてマッチングテーブルへ書き込む
人間どうしの相性なので、最後の優先度ロジックは「両者から見て近い順位」を選ぶのが直感に合います。マッチング結果は管理画面で必ず人間が確認してから送信する運用にし、ボタンを押すまでは通知メールは飛ばないようにしました。
手順5:メール送信と画像アップロード
メールは SMTP 経由の nodemailer。送信内容と宛先は mail_logs に保存し、失敗時の追跡を可能にしています。
- ワンタイムURL送信、マッチング通知、リマインダーなど用途別にテンプレート化
- SMTP 認証情報は
.envで管理(コードに直書きしない) - 大量送信時は
for...ofループ+小休止で送信レート調整
画像アップロードは multer でメモリ受信し、sharp で 長辺 1200px に縮小+JPEG変換+メタデータ削除してから保存します。これによりストレージ消費・通信量・プライバシーリスクを同時に下げられます。
手順6:管理画面の構築
管理画面は専用パスワード認証+セッション分離で運用しています。
- 管理者ログインのパスワードは SHA-256 ハッシュで
.envに保存 - ログイン成功時は
req.session.regenerate()でセッションIDを再生成(セッション固定化攻撃対策) - 管理画面のディレクトリ名は推測されにくい文字列に設定
- すべての管理操作は
audit_logsに記録(誰が・いつ・何を)
管理画面では、イベントの作成・参加者の一括CSV取込・ワンタイムURL再送・投票公開/非公開の切替・マッチング結果の確認と通知送信などを 1 つの SPA 風ページで操作できるようにしました。運営は当日 1 人で全工程を回せるのがゴールです。
手順7:Apache リバースプロキシと PM2 でデプロイ
Node.js は 3000 番で起動し、外部からは Apache 経由で受けます。
/api/*はProxyPassで Node.js に流す- それ以外のリクエストは Apache が静的HTMLとして配信
- クリーンURL(拡張子なしアクセス)は
.htaccessで内部リライト - ルート直下や存在しないパスへの直アクセスは
403を返し、親アプリにフォールバックさせない - Node 側は PM2 で起動し、
max_memory_restart: 512Mでメモリリーク予防
PM2 の pm2 startup と pm2 save でサーバー再起動後の自動起動を仕込んでおけば、運用は基本「触らない」状態に持っていけます。
Claude Code に任せるときのコツ
このシステムを作るうえで効いたコツを整理します。
- テーブル設計から始める:データ構造が固まると、ルーティングとフロントは派生で書ける
- 1 ルートずつ実装する:
POST /api/voteなら、まずバリデーション→DB保存→レスポンスの順で 1 ファイル仕上げる - セキュリティ要件は箇条書きで毎回渡す:CSP、CSRF、セッション固定化対策、ハッシュ化、レート制限など
- 運用フローを言語化する:管理画面のボタンの順序と業務フローを言葉で書くと、UI もブレない
- テスト用エンドポイントを用意する:本番DBを触らずにマッチング結果を試算できる経路を作っておく
- 環境変数の項目を最初に並べる:
.env.exampleを作っておくと、Claude Code が値を勝手にコードへ書き込みにくくなる
落とし穴で多いのは、「セッションが消える原因の見落とし」です。express-session の cookie.secure、Apache の trust proxy 設定、本番/開発の HTTPS 差、この 3 つはチェックリスト化しておくと事故が減ります。
まとめ
イベント型マッチング投票システムを Claude Code で構築する流れは、以下の通りでした。
- 要件をストーリーで Claude Code と合意する
- DB テーブル設計を先に固める
- ワンタイムURL認証 → 投票 → マッチングの順で API を実装
- メール/画像/管理画面を加えていく
- Apache リバースプロキシ+PM2 で安定運用に乗せる
この種のシステムは、要件さえ整理できれば2〜3 週間でフルスクラッチが現実的です。市販のマッチングツールでは細かい運用要件に合わないケースが多く、独自構築のほうが結果的に運用コストも下がります。
本記事は代表コバの現場知見をもとに AI で構成し、弊社にて最終確認を行っています。
「自社のイベントで使えるマッチングシステムが欲しい」「既存サービスでは要件に合わない」など、相談ベースでも対応しています。費用感だけ知りたい方も、お気軽にご相談ください。