ハンズオンの概要を説明します。

本ハンズオンでやること

本ハンズオンでやらないこと

本ハンズオンを実施する上の事前準備

Firebaseサイトにログイン

ブラウザから以下Firebaseサイトにアクセスします。

https://firebase.google.com/

右上の「ログイン」リンクを押下し、Googleアカウントでログインします。

Firebase CLIインストール

Macの場合はターミナル、Windowsの場合コマンドプロンプト(以降ターミナル)から

以下を実行します。

npm install -g firebase-tools

ターミナルからFirebaseにログインします。

firebase login

上記コマンドを入力すると、ログインする為のURLがターミナル上に表示されるので、そのURLを押下して、ログインし、アクセスの許可を行うと以下のような画面が表示され、ターなるからfirebaseに接続出来るようになります。

なお、接続を解除、または別のユーザーでログインしたい場合は、以下のコマンドを入力してください。

firebase logout

Firebaseプロジェクトの作成

Firebase サイト(https://firebase.google.com/)にアクセスし 「コンソールへ移動」をクリックします。


「プロジェクトの追加」をクリックします。

「プロジェクトの追加」画面が表示されたらプロジェクト名に「handson」と入力、「Firebase 向け Google アナリティクスのデータ共有にデフォルトの設定を使用する」のチェックを外し、「次へ」をクリックします。

「データ共有のカスタマイズ」画面が表示されたら、値を変更せず、「プロジェクトの作成」をクリックします。

しばらく待つと、プロジェクトが作成されます。

これでFirebaseを使う準備が完了します。

Hosting って何?

https://firebase.google.com/docs/hosting/?hl=ja

Firebase Hosting は、本番環境レベルのウェブ コンテンツ ホスティングです。

1 つのコマンドですばやく簡単にウェブアプリをデプロイすることができ、

静的コンテンツと動的コンテンツの両方をグローバル コンテンツ配信ネットワーク(CDN)に配信できます。

また、構成が不要な SSL が組み込まれています。

Hosting の作成

「開発」内の「Hosting」をクリックし、「使ってみる」をクリックします。

ターミナルを起動し、

mkdir sample_hosting
cd sample_hosting
firebase init

と入力します。

機能の選択が表示されるので、「Hosting」を選択します。

プロジェクトの選択が表示されるので、先程作ったプロジェクトを選択します。

ディレクトリの選択と SPA の有無を聞かれるので今回はデフォルトのままにします。

ファイルが作成されます。

デプロイ

ターミナルを起動し、

firebase deploy

と入力します。

「Hosting URL」に記載されている URL にアクセスすると、Web サイトが表示されます。

ローカル環境での実行

毎回サーバーにデプロイしないと確認できないのは面倒なので、

ローカルでサーバーを起動できるコマンドがfirebaseには用意されています。

ターミナルを起動し、

firebase serve

と入力します。

すると、ローカルでサーバーが起動し、

「Hosting」に記載されている URL にアクセスすると、Web サイトが表示されます。

「Ctrl + C」でローカル環境を終了します。

参考 : https://firebase.google.com/docs/functions/local-emulator?hl=ja

ファイルを編集しリロードすると、値が変更されることを確認してください。

Hostingの基本的な操作は以上です。

Cloud Firestore って何?

Cloud Firestore は、Firebase と Google Cloud Platform からのモバイル、ウェブ、サーバー開発に対応した、柔軟でスケーラブルな NoSQL データベースです。

リアルタイム リスナーを介してクライアント アプリ間でデータを同期し、モバイルとウェブのオフライン サポートを提供します。

これにより、ネットワークの遅延やインターネット接続に関係なく機能する アプリを構築できます。

Cloud Firestore の有効化

「開発」内の「Database」をクリックします。

画面上部「Cloud Firestore」内の「データベースの作成」をクリックします。

「セキュリティルール」画面が表示されたら、「テストモードで開始」を選択し、「有効にする」クリックします。

※テストモードでは誰でもデータの読み書きが可能となっているので後で設定を変更します。テストモードで本番運用するとセキュリティに問題の出ることがあるので気をつけてください。

しばらく待つと、「Database」画面が表示されます。

Firestoreのデータ・モデル

Firestoreのデータは、

コレクション
 →ドキュメント
 ー→フィールド
 ー→サブコレクション

に分割されています。

リレーショナル型データベースでいう

コレクション=テーブル
ドキュメント=レコード
フィールド=カラム

のイメージです。(細かくは違いますが。)

詳しくは以下URLを参照ください。

https://firebase.google.com/docs/firestore/data-model?hl=ja

ここからは実際の Web アプリを作っていきます。

作成するのはレストランのレビューサイトです。

完成イメージ

匿名ログインの有効化

Firebaseサイトにアクセスし、

「開発」内の「Authentication」をクリックします。

「ログイン方法」をクリックします。

「匿名」のステータスを「有効」にして「保存」をクリックします。

匿名ログインの詳細は以下参照。

https://firebase.google.com/docs/auth/web/anonymous-auth

コードの取得

一から作っていくと時間がかかる為、ある程度出来ているコードを作成していますので

そのコードを元に作っていきます、まずはコードを取得します。

ターミナルから以下を入力します。

cd ../
git clone https://github.com/TanakaMidnight/firebase-handson-code
cd firebase-handson-code

なお、今回フロントエンドはVue.jsとVuetifyのライブラリ使用しています。

Vue.js

https://jp.vuejs.org/

Vuetify

https://vuetifyjs.com/ja/

なお、環境構築による問題を出来るだけ起こらないようにするため、CDNで読み込んでおり、一部Vue.jsに慣れている方にとっては読みづらいコードになっています。

プロジェクトとコードの関連付け

次に取得したコードとプロジェクトを関連付けを行います。

ターミナルから以下を入力します。

firebase use --add

すると、

? Which project do you want to add?(Use arrow keys)

と表示されるので先ほど作成したプロジェクトを選択します。

次に、

? What alias do you want to use for this project? (e.g. staging)

と表示されるので

default

と入力し、関連付けを行います。

ローカル環境で確認

ローカルで実行して確認してみましょう。

firebase serve --only hosting

「Hosting」に記載されている URL にアクセスすると、Web サイトが表示されます。

FireStore にデータを書き込む

まず、データが無いのでFireStore にデータを作成していきます。

今回のアプリで使うRestaurants データは以下の通りです。

Ratings データは各 Restaurant のサブコレクションとして格納します。

以下のコードをコメントアウトしてください。

hosting/script/models.js(Line 55〜56)

const collection = firebase.firestore().collection('restaurants')
return collection.add(data)

ブラウザに戻り、ページをリロードします。

「モックデータを追加」ボタンを押下します。

画面上はまだ、データを表示するロジックを書いていませんので、変わりませんが、

Firebaseコンソール内、Firestoreにデータが表示されていますので確認してください。

Firestore からリアルタイムにデータを表示

次に作成したデータをFirestore から取得してアプリに表示します。

以下のコードをコメントアウトしてください。

hosting/script/models.js(Line 21〜24)

query = firebase
  .firestore()
  .collection('restaurants')
  .limit(50)

ブラウザに戻り、ページをリロードします。

レストランの一覧が表示されます。

このサイトを表示している状態で別ウインドウを開き、Firebaseコンソールを開き、FirestoreのRestaurantsドキュメントを選択し、レストラン名(name)を変更し、

再度サイトに戻ってみると、リロードしなくても変更したレストラン名が変更されています。

これは、リアルタイムに変更があった場合、変更を検知して自動的に更新しているためです。

リアルタイムアップデートに関する詳細は以下参照。

https://firebase.google.com/docs/firestore/query-data/listen?hl=ja

Firestore から 1 度きりのデータを表示

さきほどのコードではリアルタイムで変更があった場合、データが更新されます。

しかし、常にリアルタイムである必要が無い処理、つまり1度だけ取得したい時もあるはずです。

その場合の書き方について以下で解説します。

以下のコードをコメントアウトしてください。

hosting/script/models.js(Line 12〜16)

return firebase
  .firestore()
  .collection('restaurants')
  .doc(id)
  .get()

ブラウザに戻り、ページをリロードします。

レストランをクリックすると、詳細画面が表示されるようになります。

データの並び替えとフィルタリング

ここまででデータの取得が出来るようになり、リストが表示されるようになりました。

実際のアプリでは、並び替え(ソート)や絞り込み(フィルタ)などが必要になってくると思います。

その場合の書き方について、以下で解説します。

以下のコードをコメントアウトしてください。

hosting/script/models.js(Line 27〜40)

if (filters.category && filters.category != '') {
  query = query.where('category', '==', filters.category)
}
if (filters.city && filters.city != '') {
  query = query.where('city', '==', filters.city)
}
if (filters.price && filters.price != '') {
  query = query.where('price', '==', filters.price)
}
if (filters.sort === 'rating') {
  query = query.orderBy('avgRating', 'desc')
} else if (filters.sort === 'review') {
  query = query.orderBy('numRatings', 'desc')
}

さらに、並び替えを有効にするにはインデックスを設定する必要があります。

インデックスの設定

FireStore にインデックスを設定します。

firestore/firestore.indexes.json

{
  "indexes": [
    {
      "collectionId": "restaurants",
      "fields": [
        { "fieldPath": "city", "mode": "ASCENDING" },
        { "fieldPath": "avgRating", "mode": "DESCENDING" }
      ]
    },
    {
      "collectionId": "restaurants",
      "fields": [
        { "fieldPath": "category", "mode": "ASCENDING" },
        { "fieldPath": "avgRating", "mode": "DESCENDING" }
      ]
    },
    {
      "collectionId": "restaurants",
      "fields": [
        { "fieldPath": "price", "mode": "ASCENDING" },
        { "fieldPath": "avgRating", "mode": "DESCENDING" }
      ]
    },
    {
      "collectionId": "restaurants",
      "fields": [
        { "fieldPath": "city", "mode": "ASCENDING" },
        { "fieldPath": "numRatings", "mode": "DESCENDING" }
      ]
    },
    {
      "collectionId": "restaurants",
      "fields": [
        { "fieldPath": "category", "mode": "ASCENDING" },
        { "fieldPath": "numRatings", "mode": "DESCENDING" }
      ]
    },
    {
      "collectionId": "restaurants",
      "fields": [
        { "fieldPath": "price", "mode": "ASCENDING" },
        { "fieldPath": "numRatings", "mode": "DESCENDING" }
      ]
    },
    {
      "collectionId": "restaurants",
      "fields": [
        { "fieldPath": "city", "mode": "ASCENDING" },
        { "fieldPath": "price", "mode": "ASCENDING" }
      ]
    },
    {
      "collectionId": "restaurants",
      "fields": [
        { "fieldPath": "category", "mode": "ASCENDING" },
        { "fieldPath": "price", "mode": "ASCENDING" }
      ]
    }
  ]
}

Firebaseコンソール上でも設定できますが、deployコマンドで設定を適用できます。

ターミナルから以下を入力します。

firebase deploy --only firestore:indexes

ブラウザに戻り、ページをリロードします。

ツールバーの右にあるフィルタボタンをクリックし、正しくフィルタリングできることを確認してください。

トランザクションの設定

データの不整合を防ぐためにデータの作成、更新、削除時にトランザクションを 貼りたいことがあるはずです。

その場合の書き方について以下で解説します。

レストランの評価を登録する画面を作ります。

以下のコードをコメントアウトしてください。

hosting/script/models.js(Line 60〜77)

rating.timestamp = new Date()
rating.userId = firebase.auth().currentUser.uid
const collection = firebase.firestore().collection('restaurants')
const document = collection.doc(id)
const newRatingDocument = document.collection('ratings').doc()
return firebase.firestore().runTransaction(function(transaction) {
  return transaction.get(document).then(function(doc) {
    const data = doc.data()
    const newAverage =
      (data.numRatings * data.avgRating + rating.rating) /
      (data.numRatings + 1)
    transaction.update(document, {
      numRatings: data.numRatings + 1,
      avgRating: newAverage
    })
    return transaction.set(newRatingDocument, rating)
  })
})

上記では、レストランの平均値(averageRating)および評価数(ratingCount)の数値を更新するたし、同時にRatingsサブコレクションに評価を追加します。

トランザクションの詳細は以下を参照してください。

https://firebase.google.com/docs/firestore/manage-data/transactions?hl=ja

セキュリティの設定

最初にデータベースの作成を行いましたが、その際のセキュリティルールで「テストモード」を選択していました。

この「テストモード」はすべてのユーザーがデータベースの読み書きが行えてしまい、セキュリティに問題が発生しています。

セキュリティルールを以下で正しく設定します。

firestore/firestore.rules

service cloud.firestore {
  match /databases/{database}/documents {

    // Restaurants:
    //  - 読み取り・作成:認証済ユーザーは可能
    //  - 更新:認証済ユーザーは可能、バリデーションを行う
    //  - 削除:禁止
    match /restaurants/{restaurantId} {
      allow read, create: if request.auth != null;
      allow update: if request.auth != null
                    && request.resource.data.name == resource.data.name
      allow delete: if false;

      // Ratings:
      //  - 読み取り:認証済ユーザーは可能
      //  - 作成:認証済ユーザーでuserIdが一致している場合は可能
      //  - 更新・削除:禁止
      match /ratings/{ratingId} {
        allow read: if request.auth != null;
        allow create: if request.auth != null
                      && request.resource.data.userId == request.auth.uid;
        allow update, delete: if false;
      }
    }
  }
}

ターミナルから以下を入力します。

firebase deploy --only firestore:rules

このルールはアクセスを制限して、クライアントが安全な変更のみを行うようにします。

今回は

というルールにしています。

以上で完成です。

最後にサーバーにデプロイしてみましょう。

ターミナルから以下を入力します。

firebase deploy

今までローカルで作業していたコードがサーバーにデプロイされました。

実際に確認してみましょう。

以上でハンズオンは終了です。

お疲れ様でした。

早く終わった人は以下の機能を実装してましょう。

また、他のアプリを作ってみたい方は以下のcodelabに挑戦してみましょう。

https://codelabs.developers.google.com/codelabs/firebase-web/

上記コンテンツは英語ですが、

英語がわからなくてもGoogle翻訳などで翻訳すればある程度理解できると思います。