概要
社内の懇親イベントで、Web上での投票ページが必要になりました。
しかし1回きりのイベントなので、そんなに工数をかけたくない。
そうだ、Firebaseを使ってみよう!
Firebaseで何ができる?
Firebase統合バックエンドサービスであり、
本来自力でサーバーサイドで構築するようなDBや認証機能などを提供しています。
またWeb,iOS,Androidなど多くのプラットフォームに対応しています。
従量課金のサービスではありますが、小規模な開発には充分な無料枠が設定されています。
(Sparkプランの場合。大規模向けのBlazeプランは完全従量課金制)
料金の詳細はこちら
コンソール画面から各種サービスの利用を開始することができます。
Firebaseの主な機能
アナリティクス(Analytics)
Googleが提供するGCPのサービスであるため、Googleアナリティクスが標準で利用できます。
Firebaseコンソールの管理画面でアクセス推移の確認が容易にできます。
データベース(Realtime Database)
NoSQL形式のデータベース。
変更が即座に接続されているデバイスに反映されるリアルタイムの同期機能をもちます。
認証(Authentication)
メール・パスワードでのログイン機能のほか、
TwitterやFacebookなどのSNS認証や、電話番号によるSMS認証(有料)も組み込めます。
ストレージ(Cloud Storage)
クラウドストレージが5GBまで無料で利用できます。
サーバーレスコンピューティング(Cloud Functions)
ファンクション単位で実行できるサーバーレス機能。
データベースの更新をトリガーにして起動させるといったことが可能です。
作りたいもの
- 立候補者が複数名いる、賞争いの投票システム
- 不特定多数の参加者が、候補者うちいずれかに投票する
- 参加者1人につき、1回のみ投票できる
今回は投票結果を保持するためにRealtime Databaseを、
投票のタイミングで集計を行うためにCloud Functionsを利用しました。
また、投票回数はクッキーを用いて制限しましたが、Firebaseの機能とは関係がないため割愛します。
Firebaseアプリ開発を始める







<!-- すべてのFirebaseアプリで必要 -->
<script src="https://www.gstatic.com/firebasejs/7.5.0/firebase-app.js"></script>
<!-- Realtime Databaseを利用するには下のSDKを追加で読み込ませます -->
<script src="https://www.gstatic.com/firebasejs/7.5.0/firebase-database.js"></script>
<!-- 投票のスクリプト -->
<script src="/path_to/vote.js">
注: セキュリティルールについて

実際に投票システムを作る
アプリの初期化
//Firebaseの接続設定
const firebaseConfig = {
apiKey: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
authDomain: "PROJECTNAME.firebaseapp.com",
databaseURL: "https://PROJECTNAME.firebaseio.com",
projectId: "PROJECTNAME",
storageBucket: "PROJECTNAME.appspot.com",
messagingSenderId: "00000000000",
appId: "1:XXXXXXXXXXXX:web:XXXXXXXXXXXXXXXXXXXX",
measurementId: "XXXXXXXXXXX"
};
//初期化
firebase.initializeApp(firebaseConfig);
投票処理
firebase.database()
でRealtime Databaseにアクセスできます。
RealtimeデータベースはNoSQLであり、JSON形式でデータを保持します。
パスを指定してその階層に読み書きを行うようなイメージです。
{
votes: {
LwY0fIos2fdKi46zvuw: {
for: 'Andy',
created: '2020/02/02 02:02:02',
},
LwY0px7Rx7y4qISIiJq: {
for: 'Bob',
created: '2020/02/02 02:03:04',
},
}
}
今回は一票ごとにユニークなキーを持たせ、
そのメンバーとして投票に関する候補者や時刻などの情報を持たせました。
...
//投票する
function vote (candidate) {
//票ごとにユニークなキーを取得する
let key = firebase.database().ref('votes').push().key;
//投票データ
let values = {
'for': candidate,
'created': new Date().toLocaleString(),
};
//作成したキーに投票データを代入する
let updates = {};
updates['/votes/' + key] = values;
//データ更新
firebase.database().ref().update(updates);
}
ここで送信したデータは、コンソール上で確認することができます。

集計処理
投票データはこれでFirebase上に保存できましたが、
結果表示用に、このデータを集計したものが欲しいです。
Cloud Functionの機能を用いると、
Realtime Databaseに更新をトリガーにして処理を実行させることができます。
npm install -g firebase-tools
Firebase CLI用いてローカルで開発し、Firebase上にデプロイします。
https://firebase.google.com/docs/functions/get-started?hl=ja
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
//votesに書き込みがあったタイミングで実行される
exports.countVotes = functions.database.ref('/votes').onWrite(change => {
// onWriteに渡されるオブジェクトは、変更前beforeと変更後afterにわかれている
let changed = change.after.val();
let countAll = Object.keys(changed).length;
// 候補者
const candidates = ['Andy', 'Bob'];
// 得票数countの初期化
let count = {
'all': countAll,
}
for (let cndkey in candidates) {
count[candidates[cndkey]] = 0;
}
//{'all': 0, 'andy': 0, 'bob': 0}
// 集計
for (let key in changed) {
for (let cndkey in candidates) {
if (changed[key]['for'] === candidates[cndkey]) {
count[candidates[cndkey]] += 1;
}
}
}
//votesと同じ階層に、集計結果countを保存
return change.after.ref.parent.child('count').set(count);
});
データベースのvotesに変更があったタイミング(onWriteイベント)で、すべての投票を集計し、
countに保存するトリガー関数を作成します。
作り終わったら
firebase deploy --only functions


デプロイした関数はコンソール上で確認することができます。

votesに投票データが挿入されるたびに、countが更新されます。
結果取得処理
Realtime Databaseを用いると、
データに変更があったタイミングでイベント(valueイベント)を発生させることができます。
今回の場合は投票がされたタイミングでリアルタイムに結果に反映されることになります。
function getCount () {
let counts = firebase.database().ref('/count');
//countが更新されるたびに実行
counts.on('value', snapshot => {
console.log(snapshot.val());
//データ表示処理
});
}
まとめ
結論から言うと、Firebase自体の学習コストは多少あるので、
普通のRDBS使った方が場合によっては早く実装できるかもしれません。