Vue.js + Firebaseでユーザー別のデータ保存・取得を実装する

メモアプリを例にして、ユーザー別にメモ内容を保存・取得できるコードを示していきます。
Firebaseを使用したのは初めてなので、未熟な部分があるかもしれません。
また、FirebaseのUIは日々更新されているようなので、時期によっては添付のスクリーンショットとUIが異なる可能性が有ります。

※事前にGoogleアカウントを用意してください
※Firebaseを用いたウェブアプリ作成が初めての場合、まずはcodelabsのチュートリアルをこなすことをおすすめします

要件

  • メモアプリはVue.js(Vue CLI)で実装します
  • データ永続化にはFirebaseのRealtime Databaseを使用します
  • ユーザーごとにメモを保存・取得できるように、FirebaseでGoogleアカウントによる認証機能を設けます

プロジェクトの作成

Firebaseにアクセスし、まずはプロジェクトを作成します。
f:id:konaga-k:20210821154143p:plain

プロジェクトの作成手順で、プロジェクトに適当な名前を付けます。
直後にGoogleアナリティクスの有効化について聞かれますが、今回は関係ないので無効でいいです。

プロジェクトを作成したら、アプリからFirebaseにアクセスできるよう、スニペットを取得します。
プロジェクトの詳細ページから「ウェブ」をクリックします。
f:id:konaga-k:20210821155022p:plain

「ウェブアプリへの Firebase の追加」画面が開きますので、アプリを登録します。
ホスティングは行わないのでチェックは外します。
f:id:konaga-k:20210821155117p:plain

続けて、Firebase SDK の追加について指示されます。
f:id:konaga-k:20210821155655p:plain

まず、htmlファイルに以下のコードを埋め込みます。Firebaseのプロダクトを利用するために必要です。

index.html

<!DOCTYPE html>
<html lang="ja">
  <head>
    <!-- ... -->

    <script src="https://www.gstatic.com/firebasejs/8.7.1/firebase-app.js"></script>
    <script src="https://www.gstatic.com/firebasejs/8.7.1/firebase-auth.js"></script>
    <script src="https://www.gstatic.com/firebasejs/8.7.1/firebase-database.js"></script>
  </head>

また、アプリの初期化についてもbodyタグ内で行うよう指示されますが、
今回はFirebaseインスタンスApp.vueで使いたいので、JSファイルに定義するようにします。

src/firebase.js

import firebase from 'firebase'

const firebaseConfig = {
  apiKey: "XXXX",
  authDomain: "memo-app-a4aa9.firebaseapp.com",
  projectId: "memo-app-a4aa9",
  storageBucket: "memo-app-a4aa9.appspot.com",
  messagingSenderId: "XXXX",
  appId: "XXXX"
};

firebase.initializeApp(firebaseConfig);

export { firebase }

あとは、FirebaseのJSライブラリをインストールします。

$ npm install firebase

ユーザー認証の実装

まず、利用する認証についてFirebase側で有効にします。
「Authentication」から「Sign-in method」タブを開き、Googleアカウントでの認証を有効にします。
f:id:konaga-k:20210821161512p:plain

続いて、アプリケーション側でサインイン・サインアウトを可能にします。

src/App.vue

<template>
  <header class="memo-header">
    <div>
      <span @click="signIn">サインイン</span>
      <span @click="signOut">サインアウト</span>
    </div>
  </header>
  <!-- ... -->
</template>

<script>
import { firebase } from './firebase.js'

export default {
  methods: {
    signIn: function () {
      const provider = new firebase.auth.GoogleAuthProvider();
      firebase.auth().signInWithPopup(provider);
    },
    signOut: function () {
      firebase.auth().signOut();
    }
    // ...

オプションですが、ユーザーのサインイン状況に応じてレンダリング内容を変更することができます。

src/App.vue

<template>
  <div>
    <h1>メモアプリ</h1>
    <div v-if="!signingIn">
      <p>サインインしてください</p>
    </div>
    <div v-else class="memo-container">
      <!-- メモの一覧や入力フォーム -->
    </div>
  </div>
</template>

<script>
import { firebase } from './firebase.js'

export default {
  data: function() {
    return {
      signingIn: false,
      currentUser: null,
      // ...
    }
  },
  beforeCreate: function() {
    firebase.auth().onAuthStateChanged((user) => {
      if (user) {
        this.currentUser = firebase.auth().currentUser
        this.signingIn = true
          } else {
            this.memoItems = []
          }
        })
      } else {
        this.currentUser = null
        this.signingIn = false
      }
    })
  },

これでユーザー別にデータ保存・取得を行う準備が整いました。

Realtime Databaseへのメモ永続化の実装

Firebase側の準備から行います。
「Realtime Database」からデータベースの作成を行います。ルールはあとで変更するので、一旦テストモードにします。
f:id:konaga-k:20210821163131p:plain

次に、ユーザー別にメモを保存・取得できるようにします。

src/App.vue

<script>
import { firebase } from './firebase.js'
const dbKey = 'memoItems'
export default {
  name: 'App',
  data: function() {
    return {
      signingIn: false,
      currentUser: null,
      memoItems: [],
      nextItemId: 1
    }
  },
  beforeCreate: function() {
    firebase.auth().onAuthStateChanged((user) => {
      if (user) {
        this.currentUser = firebase.auth().currentUser
        this.signingIn = true
        firebase.database().ref(dbKey).child(this.currentUser.uid).on('value', (snapshot) => {
          if (snapshot.val()) {
            this.memoItems = Object.values(snapshot.val()) // Realtime Databaseからの取得処理
            this.nextItemId = this.memoItems[this.memoItems.length - 1].id + 1
          } else {
            this.memoItems = []
          }
        })
      } else {
        this.currentUser = null
        this.signingIn = false
      }
    })
  },
  methods: {
    saveMemos: function() {
      firebase.database().ref(dbKey).child(this.currentUser.uid).set(this.memoItems) // Realtime Databaseへの保存処理
    },
    createItem: function() { // メモ作成時に呼び出す
      const newItem = { id: this.nextItemId,  body: '新規メモ' }
      this.memoItems.push(newItem)
      this.saveMemos()
    },
    // ...

このとき、Realtime Databaseには以下のような構造のデータが入ります。
隠している部分にはuidが入っています。
f:id:konaga-k:20210821164936p:plain

さて、これでユーザー別にメモの保存・取得ができます。
ただし、不正な書き換えによって、他のユーザーのデータを保存・取得できてしまう余地がありそうです。
これを防ぐため、Firebase側で「Realtime Databese」の「ルール」からセキュリティルールを設定します。

f:id:konaga-k:20210821165248p:plain

{
  "rules": {
    "memoItems": {
      "$uid": {
        ".read": "$uid === auth.uid",
        ".write": "$uid === auth.uid"        
      }
    }
  }
}

以上で、ユーザー別のデータ保存・取得の実装は完了です。