15
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Firestoreのデータを可視化・監視しアラートする、かんたんなWEBアプリ(Nuxt.js,GCP Firestore)

Last updated at Posted at 2019-11-29

はじめに

Nuxt.jsの勉強がてら、Firestoreに格納されているデータをブラウザに表示させつつ、リアルタイムに変更があった際に、その更新が反映される簡易WEBアプリを作成しました。

アクションとしては、3つの仕組みを用意しました。
①トグルボタンでのFirestoreデータ更新(True/False)
②削除ボタンでのFirestoreデータ削除
③更新(追加、変更、削除)があった際、ブラウザ上アラートが上がる

作成したNuxt.jsプロジェクトは、Firebase Hostingにデプロイして動作確認し、
手動デプロイができたので、GCP CloudBuildを使って、CIの仕組みも少し試験しました。

見た目はこんなものです。(CSSについては特に今回は言及しません。)
このカラム名で、データがドキュメント毎に保存(図にあるのは二つ)されている形になります。

UI

##技術要素

  • Firestore
  • vue.js / Nuxt.js
  • Firebase Hosting
  • GCP CloudBuild

説明

    1. create-nuxt-appでNuxt.jsプロジェクト作成
    1. Firestoreへ接続するコード準備
    1. Storeの設定
    1. 表示部分のページ作成
    1. ローカルで動作確認
    1. Firebase Hostingへデプロイ
    1. CloudBuildでCI環境構築

1. create-nuxt-appでNuxt.jsプロジェクト作成

まず、SEOなどは気にせず、今回はSPA(Single Page Application)でお手軽に作成したいので、SPAを選択しました。
参考サイトは、Nuxt.jsを使うときに、SPA・SSR・静的化のどれがいいか迷ったらです。

基本的にはデフォルトで、SPAを選択する箇所のみ変更。

npx create-nuxt-app nuxt-view-firestore-status

必要に応じて、npmパッケージはインストールしておきます。後々利用します。

npm install --save firebase
npm install --save vuexfire

2. Firestoreへ接続するコード準備

必須ではないですが、dotenvを使って環境変数にプロジェクトIDと後々使うFirestoreのコレクション名を格納しようと思います。nuxt.config.jsのmodulesに、にdotenvを設定。
そして、.envファイルは、作成されたフォルダ直下に新規作成。

nuxt.config.js
  modules: [
    '@nuxtjs/dotenv'
  ],
.env
FIREBASE_PROJECT_ID = '<your project id>'
FIRESTORE_COLLECTION_NAME = '<your firestore collection name>'

次に、Firestoreの初期コンフィグを定義する、Vueプラグインを作成します。pluginsフォルダ配下に、firebase.jsを作成します。

plugins/firebase.js
import firebase from 'firebase'

const config = {
    projectId: process.env.FIREBASE_PROJECT_ID
}

if (!firebase.apps.length){
    firebase.initializeApp(config)
}

export default firebase

3. Storeの設定

今回は、ストアの利用方法の「クラシックモード」と「モジュールモード」のうち、モジュールモードで作成します。構成は以下です。

Nuxt.jsのストアをモジュールモードで使用するときのTips

Nuxt.js + Firebase で vuexfire を使って Cloud Firestore と連携する(データバインド編)

store
├── index.js
└── view_status.js

index.jsでは、vuexfire の vuexfireMutations を定義するのみ。

store/index.js
import { vuexfireMutations } from 'vuexfire'

export const mutations ={
    ...vuexfireMutations
}

次に、view_status.jsに、Firestoreの読み込みと、ステート、アクション定義。

アクション名 説明
init Firestoreから取得するデータをバインド
remove 削除ボタンが押されたときに呼び出す。ドキュメント名を引数にFirestoreのドキュメント削除
toggle トグルボタンが押されたときに呼び出す。対象ドキュメントのkeyがdoneの値をTrue/Falseへ変更させる。(Todosサンプルアプリの流用なのであまり気にしないでください。)
popup Firestoreの対象Collection内のドキュメントが追加、削除、更新が発生した際に、アラートを上げる機能
store/view_status.js
import firebase from '~/plugins/firebase'
import { firestoreAction } from 'vuexfire'

const db = firebase.firestore()
const statusRef = db.collection(process.env.FIRESTORE_COLLECTION_NAME)

export const state = () => ({
  statuses: []
})

export const actions = {
  init: firestoreAction(({ bindFirestoreRef }) => {
    bindFirestoreRef('statuses', statusRef)
  }),
  remove: firestoreAction((context, id) => {
    statusRef.doc(id).delete()
  }),
  toggle: firestoreAction((context, status) => {
    statusRef.doc(status.id).update({
      done: !status.done
    })
  }),
  popup: firestoreAction((context) => {
    statusRef.onSnapshot(function(snapshot){
      snapshot.docChanges().forEach(function(change){
        if (change.type === "added"){
          alert("ドキュメントが追加されました。\r\nIDは、" + change.doc.id + "です。")
        }
        if (change.type === "modified"){
          alert("ドキュメントが変更されました。\r\nIDは、" + change.doc.id + "です。")
        }
        if (change.type === "removed"){
          alert("ドキュメントが削除されました。\r\nIDは、" + change.doc.id + "です。")
        }
      })
    })
  })
}

注意

FirestoreのCollectionのルール設定は、今回、開けてテストを実施しましたが、本番利用時はセキュリティ設計の考慮が必要です。FirebaseコンソールのDatabase -> 「ルール」でGUIで設定可能です。

Firebaseのサイトの安全でないルールを修正するを参照。

    match /<your collection name>/{document=**} {
      allow read, write: if true;
    }

4. 表示部分のページ作成

最後に、表示部分のページ作成するため、pagesフォルダに、閲覧用のVueファイルを作成します。status.vueとしました。

script      説明
created ページが開いた時の処理。Vue インスタンスが生成された直後に呼び出されます。今回は、上記で定義した、initとpopupアクションを呼び出しています。
methods vueテンプレートから呼び出すメソッドで、remove(id)とtoggle(status)を定義しました。上記で定義したremoveとtoggleアクションを引数設定しつつ呼び出します。
computed 値が変わると再評価される仕組みで、今回はstoreの値を見て変化があれば更新されるようにしています。
pages/status.vue(抜粋)
    created: function() {
      this.$store.dispatch('view_status/init')
      this.$store.dispatch('view_status/popup')
    },
    methods: {
      remove(id) {
        this.$store.dispatch('view_status/remove', id)
      },
      toggle(status) {
        this.$store.dispatch('view_status/toggle', status)
      },
    },
    computed: {
      view_status() {
        return this.$store.getters['view_status/orderedStatus']
      }
    },

テーブルで取得したFirestoreのデータを表示しています。

pages/status.vue(抜粋)
<template>
  <div>
    <table>
      <tr>
        <th>-</th>
        <th>done</th>
        <th>session_id</th>
        <th>url</th>
        <th>timestamp</th>
        <th>remove</th>
      </tr>
      <tr v-for="status in view_status" :key="status.id">
        <td>
          <input type="checkbox" v-bind:checked="status.done" @change="toggle(status)">
        </td>

        <td>{{status.done}}</td>
        <td> {{status.session_id}}</td>
        <td> <a v-bind:href="status.url">{{status.url}}</a></td>
        <td> {{status.timestamp.toDate() | dateFilter}}</td>
        <td>
          <button v-on:click="remove(status.id)">remove</button>
        </td>

      </tr>
    </table>
  </div>
</template>

簡単にコメントすると、

pages/status.vue(抜粋)
<tr v-for="status in view_status" :key="status.id">

view_statusで取得した値(=ドキュメント一覧)をドキュメント毎に、v-forで繰り返し処理しています。

pages/status.vue(抜粋)
<input type="checkbox" v-bind:checked="status.done" @change="toggle(status)">

UI上、一番左のカラムにチェックボックスを配置し、チェックON/OFFは、ドキュメント(status)のdoneの値をバインドしています。変更があった場合は、toggleメソッドを呼び出します。

pages/status.vue(抜粋)
<a v-bind:href="status.url">{{status.url}}</a>

ドキュメント(status)のurlの値にURLの文字列が格納されているのですが、最初はmustacheタグ{{status.url}}で表示させようとしましたが、それだとリンクがつかないので、aタグでリンクさせたい場合は、このようにバインドさせて表示させる必要があるようです。

pages/status.vue(template内)
<td> {{status.timestamp.toDate() | dateFilter}}</td>
pages/status.vue(script内)
  import moment from 'moment'
//(省略)
    filters: {
      dateFilter: function(date) {
        return moment(date).format('YYYY/MM/DD HH:mm:ss')
      }
    }

タイムスタンプの値を、指定の形式で表示させようとする場合は、フィルタ機能を使って実現させると良いようです。momentライブラリを使いました。

pages/status.vue(抜粋)
  <td>
     <button v-on:click="remove(status.id)">remove</button>
  </td>

timestampを降順にする

computedのところでは、storeのgettersを呼び出していますが、timestampの順序を変える仕組みを導入しました。日付でソートをかけるために、webpackプラグイン(lodash)をinstallしています。

webpack プラグインを追加するには?と、細かすぎるけど伝わって欲しいlodash.jsの話を参照。

nuxt.config.js
const webpack = require('webpack')

// (省略)

  build: {
    /*
    ** You can extend webpack config here
    */
    extend (config, ctx) {
    },
    plugins: [
      new webpack.ProvidePlugin({
        '_': 'lodash'
      })
    ]
  }

そのうえで、gettersをview_status定義し、降順でtimestampが並ぶように処理をします。

store/view_status.js
export const getters = {
  orderedStatus: state => {
    return _.orderBy(state.statuses, 'timestamp', 'desc')
  }
}

5. ローカルで動作確認

ローカルでの検証は npm run devで動作させ、localhost:3000/status上で正常に動くことを確認しました。

再掲になりますが、以下のようなUIで、動作が問題なく動きました。

  • 一番左のチェックボックスをクリックすると、Doneの値がtrueへ更新
  • 再度クリックするとDoneの値がfalseに更新
  • 一番右のremoveボタンをクリックすると対象データが削除
  • 上記アクションを実施した際に、アラートが画面上上がること

UI

6. Firebase Hostingへのデプロイ

まず、Nuxtプロジェクトをビルドします。 npm run build
Firebaseプロジェクトを firebase init コマンドで初期化します。

注意としては、hostingを選択することと、build後に生成されるフォルダはdist(not public)なので、distを指定することです。

上記を実施後、firebase deployコマンドでデプロイします。
問題がなければ、 https://<your project>.firebaseapp.com/status/ などにアクセスして確認できます。

7. CloudBuildでCI環境構築

CloudBuildでCI環境構築構築し、githubにコードをデプロイすると、CloudBuildがトリガーされ、FirebaseHostingへ自動デプロイの仕組みも作りました。CloudBuild側の設定は、別途サイトを参考ください。
準備としては、Dockerfileを作成し、cloudbuild.yamlで設定を入れることで動作しました。
ちなみに、.gitignoreファイルに.envが記載されているので、CloudBuildで連携する場合は、.envもリポジトリに上げるようにする必要があります。

Dockerfile
# use latest Node 10
FROM node:10
# install Firebase CLI
RUN npm install -g firebase-tools

ENTRYPOINT ["/usr/local/bin/firebase"]
steps:
# Install
- name: 'gcr.io/cloud-builders/npm'
  args: ['install']
# Build
- name: 'gcr.io/cloud-builders/npm'
  args: ['run', 'build']
# Docker Image
- name: 'gcr.io/cloud-builders/docker'
  args: ['build', '-t', 'gcr.io/$PROJECT_ID/firebase', '.']
# Deploy
- name: 'gcr.io/$PROJECT_ID/firebase'
  args: ['deploy', '--project', '$PROJECT_ID']
# push built images to Container Registry
images: ['gcr.io/$PROJECT_ID/firebase']

#おわりに
Vue.js / Nuxt.js のおかげで比較的簡単に、ブラウザ上でFirestoreデータを確認し、更新があったらアラートが上がる機能を作成できました。

もう少し応用的なものも作ってみたいと思うのと、環境面では、FirebaseHostingではなく、Google App Engineにもデプロイができるようなので、そちらも試してみたいと思います。

コードはgithubに公開しました。

参考コンテンツ

15
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?