順番待ちSaaSをFirebaseだけで作る(採番の競合をルールで防ぐ・列挙禁止・リアルタイム)
自作の順番待ちSaaS「ナラビ」(narabi.autoarticles.net)は、バックエンドのサーバーを持たず、Firebase(Firestore + Auth)だけで動く。飲食店の受付でQRから番号を取り、スタッフ画面がリアルタイムで進む。この構成で一番むずかしいのが採番だ。複数人が同時に「次の番号」を取ろうとしたとき、同じ番号を配ってはいけない。サーバーが無い前提で、これをどう安全にするかを説明する。
採番の競合を「ルール」で防ぐ
肝は、番号の発行を店舗ドキュメントの lastIssued カウンタで管理し、その +1 以外の更新を Firestore セキュリティルールで拒否することだ。実際のルール(抜粋)はこうなっている。
1 | |
匿名の顧客が触れるのは lastIssued フィールドだけ(hasOnly)で、しかも「現在値ちょうど+1」にしか書けない。二人が同時に同じ現在値から+1しようとすると、片方のトランザクションだけが成功し、もう片方は失敗して再試行する。番号の飛びも重複も起きない。サーバーコードを一切書かずに、採番の一貫性をルールだけで保証している。
列挙禁止となりすまし対策
権限の源泉は親ドキュメントの ownerUid に置き、chicken-and-egg(権限を確認するために権限が要る)を避けている。店舗一覧の list は allow list: if false で禁止し、IDを知る人だけが get できる(enumeration 対策)。チケットも、顧客本人は自分のチケットの「取消」だけ、しかも差分フィールドを hasOnly(['status','cancelledAt']) に限定して許可する。「誰が・どのフィールドを・どんな値にできるか」をルールで絞り込むのが、サーバーレスの安全設計の核心だ。
リアルタイム反映は Firestore の onSnapshot に任せる。番号が進めば全画面が自動で更新される。ポーリングも WebSocket 実装も要らない。
実際に試す
Firestoreエミュレータで、lastIssued+1 制約と同時採番の競合を手元で確認する。Node 18以上・firebase-tools が必要。
まずルールを firestore.rules に保存する。
1 | |
エミュレータを起動する。
1 | |
別ターミナルで、同時に2人が採番する競合テストを走らせる(npm i firebase 済みのフォルダで)。
1 | |
1 | |
期待される出力:
issued: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]のように、重複も飛びもなく連番が配られる。- ルールを外して +1 以外の値を書こうとすると
PERMISSION_DENIEDになる(ルールが採番の一貫性を守っている証拠)。
実際の受付・スタッフ画面は ナラビ で確認できる。
まとめ
- 採番はカウンタ +1 をセキュリティルールで強制し、トランザクションの再試行で競合を吸収する。
list禁止・ownerUid起点・差分フィールド限定で、サーバーレスでも権限を厳密に絞る。- リアルタイムは
onSnapshotに任せ、ポーリングもWebSocketも書かない。
このバージョンはその範囲に入るのか
依存の話でいちばん間違えやすいのは ^1.2.3 や ~1.2.3 がどこまでを許すかです(^0.2.3 のようにメジャーが 0 のときは規則が変わります)。範囲とバージョンを貼ると、下限・上限に展開したうえで一致・不一致とその理由を表示するsemver 範囲判定ツールを置いています。ブラウザの中だけで動き、入力はどこにも送信しません。