開発期間は 2日間。
約 60人規模 の結婚式で利用し、費用は 0円 でした。
はじめに
ある日、結婚式を控えている非エンジニアの友人からこんな相談を受けました。
友人:「プロポーズの日が七夕だから、ゲストが入力したお祝いメッセージを流れ星にしてスクリーン投影したい」
なんて素敵なアイデアなんだ…!と感銘を受け、ぜひ力になりたいと思い快諾しました。
しかも驚いたことに、友人はChatGPTを使ってすでにプロトタイプを作っていました。
メッセージ投稿機能と流れ星アニメーションは完成済みです。
困っていたのは、
「ローカルで動いているこのアプリを、どうやって公開すればいいのか?」
という、いかにも非エンジニアらしい部分でした。
ただホスティングするだけでは、リアルタイムで他の人の投稿を見ることはできません。
投稿を保存し、全員に即時配信する仕組みが必要になります。
とはいえ、お金をもらうわけでもないので、どうにか無料で、かつ当日トラブルなく動く構成にしたい。
そこで選んだのが Firebase でした。
結果として、60人規模の結婚式でも問題なく動作し、無料で運用することができました。
どんなアプリを作ったか
利用シーン
- 受付でQRコードを来場者に配布して、スマホから好きなタイミングでお祝いメッセージを投稿してもらう
- 中座に投稿タイムを設けて、スクリーンに上映する
機能要件
- URLを固定にして、QRコードでアクセス可能にする
- ログイン不要にして気軽に投稿してもらう
- 他の人のメッセージをリアルタイムで見られるようにする
- 式が終わったあと新郎新婦にメッセージを提供する
技術構成
使用技術
- アプリ
- HTML / CSS / Javascript
- インフラ
- Firebase Hosting
- Firebase Realtime Database
Firebaseを選んだ理由
今回の肝はリアルタイム通信の実現でした。
自前でWebSocketサーバー(SSE、WebSocketなど)を構築する選択肢もありました。
しかし、披露宴当日にサーバー管理者としてヒヤヒヤするのではなく、ゲストとして安心して楽しみたい。
そのため、運用負荷がほぼゼロなマネージドサービス一択でした。
次に、マネージドサービスの選択肢としては、AWS、無料枠が多いGCP、Firebaseがありました。
AWSは普段使い慣れていましたが、完全無料は難しいです。
一方、Firebaseは一定の枠に収まれば完全無料で使うことができます。
Firebase の料金・無料枠(Hosting & Realtime Database)
※無料枠を超えるとシステムが使えなくなるので注意
| 項目 | 料金 |
|---|---|
| Firebase Hosting – ストレージ | 最大 10 GB |
| Firebase Hosting – 転送量 | 10 GB/月 |
| Realtime Database – 保存データ容量 | 1 GB |
| Realtime Database – ダウンロード量 | 10 GB/月 |
| Realtime Database – 同時接続数 | 最大 100 接続 |
今回は60人規模の結婚式と聞いていたので、同時接続数 最大100接続に収まるという見込みでFirebaseを採用しました。
実装
アニメーション
流れ星のアニメーションはJavascriptで、画面のデザインは素のHTML/CSSで実装しました。
完成系のイメージは既に友人に見せてもらっていたので、Cursorにプロンプトを投げて実装してもらいました。
リアルタイム通信
リアルタイム部分は、実装だけで言えば1時間もかかっていません。
アニメーションの微調整よりも早く終わりました。
ページ読み込み時の制御を入れたりしてますが、2つの関数で完結しています。
sendMessage():メッセージ送信でデータベースに登録する
onChildAdded():リアルタイム監視してデータベースにメッセージが投稿されたことを検知する
const firebaseConfig = {
authDomain: "XXXXXX.firebaseapp.com",
databaseURL: "https://XXXXXX.firebaseio.com/",
projectId: "XXXXXX",
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
// Initialize Realtime Database and get a reference to the service
const database = getDatabase(app);
// ページ読み込み完了後に新しいメッセージを監視開始
let isPageLoaded = false;
let pageLoadTimestamp = Date.now(); // スクリプト読み込み時点で時刻を記録
const messagesRef = ref(database, 'messages');
// ページ読み込み完了後に新しいメッセージのみを監視
onChildAdded(messagesRef, (snapshot) => {
if (isPageLoaded) {
const newMessage = snapshot.val();
// ページ読み込み完了後に送信されたメッセージのみを表示
if (newMessage.timestamp >= pageLoadTimestamp) {
console.log('新しいメッセージ:', newMessage);
createShootingStar(newMessage.message, false);
}
}
});
function writeUserData(database, message) {
// より安全な一意ID生成(タイムスタンプ + ランダム文字列)
const timestamp = Date.now();
const randomId = Math.random().toString(36).substring(2, 15);
const uniqueId = `${timestamp}_${randomId}`;
set(ref(database, `messages/${uniqueId}`), {
message: message,
timestamp: timestamp
});
}
/**
* メッセージを送信する関数
*/
function sendMessage() {
const message = messageInput.value.trim();
// 空のメッセージはスキップ
if (!message) {
return;
}
// 流れ星として表示(onChildAddedで表示される)
writeUserData(database, message);
// 入力欄をクリア
messageInput.value = '';
messageInput.focus();
}
落とした要件
セキュリティルール
Realtime Databaseにはセキュリティルールの設定が必要です。
今回はイベント用途という前提で割り切り、簡易的なルールで運用しました。
本番サービスであれば、認証や書き込み制限は必須です。
{
"rules": {
"messages": {
".read": true,
".write": true
}
}
}
入力メッセージチェック
本来であれば、不適切な投稿を防ぐために特定ワードのブロックやバリデーションを実装すべきです。
しかし今回は、利用シーンが限定的であり、参加者も把握できているクローズドな環境だったため、実装コストとの兼ね合いで実装しませんでした。
結果として、大きなトラブルは発生しませんでした。
60人同時接続で本当に問題なかったのか?
保存データ容量については、テキストしか送れないチャットアプリなので 保存容量 1GB は全く問題ありませんでした。
心配していた同時接続数も問題ありませんでした。
実際には全員が同時に開く時間はほとんどなく、ピークでも想定内に収まっていたようです。
つまり今回の規模(60人で5時間利用)であれば、
Firebaseの無料枠で十分現実的に運用できることが分かりました。
終了後のメッセージ提供
チャットアプリの開放時間は5時間くらいでしたが、なんと135メッセージも集まりました。
式が終わったあと、すぐにRealtime DatabaseのデータをJSONエクスポートし、整形して新郎新婦に渡しました。
心温まるメッセージばかりで、とても喜んでいました。
メッセージを一部抜粋
2025-XX-XXT10:56:33 この度はおめでとうございます🥂素敵な2人の門出を一緒にお祝いできることとても嬉しく思います!
2025-XX-XXT11:19:26 ウェルカムルームでおいしいドリンクをいただきました
2025-XX-XXT13:52:28 挙式の時は涙が出そうになりました、おめでとう
2025-XX-XXT14:57:30 お二人らしいとっても素敵な式でした!末長くお幸せに〜❣️
リアルタイムで流れていく体験だけでなく、データとして思い出を残せたことが、今回の開発で一番嬉しかったポイントでした。
最後に、今回開発したアプリは技術的な難易度だけでいえば決して高いものではありません。
それよりも、私がエンジニアとして友人の幸せな1日を祝福できたことは、大きな喜びでした。
依頼してもらった友人に感謝しかありません。
