Vue3+FirebaseでリアルタイムCRUD操作ができるアプリを作成しよう

今日はFirebaseのFirestoreデータベースを使ってリアルタイムで更新される、つまり、他のブラウザで更新されたデータベースが自分のブラウザでも更新されるCRUDアプリを作成します。

記事を始める前に知っておくべきこと

  • Vue3の基本
  • Firebaseのプロジェクトが作成できていること
  • Node.jsがインストールされていること

完成形はこのようになります。

完成したコードはGitHubから確認してください。

VueのToDoアプリ

今日はVueの説明は省きますがこちらのスターターテンプレートを使用するとFirebaseに集中して理解ができると思います。Gitでクローンして使ってください。

Vue-ToDoリストスターター

今日使用するテクノロジーはこちらです。

  • Vue3
  • TailwindCSS(オプショナル)
  • Firebase←今日追加する部分

ToDoスターターのリポジトリの説明

基本的に理解しておく部分はApp.vueのみです。

App.vue

<template>
  <div class="flex justify-center w-auto">
    <div class="grid justify-items-center">
      <h1 class="text-3xl py-3 mt-4">ToDoリスト</h1>

      <div class="flex my-4">
        <input
          v-model="todoInput"
          class="border-solid border-2 rounded-md"
        />
        <button
          @click="addTodo"
          class="bg-indigo-500 hover:bg-cyan-600 rounded-md text-white py-2 px-4 mx-2"
        >
          追加
        </button>
      </div>

      <div
        v-for="todo in todos"
        key="id"
        class="p-3 m-2 bg-green-300 rounded-md min-w-full flex items-center justify-between"
      >
        <p :class="{ 'line-through	': todo.isCompleted }">{{ todo.title }}</p>
        <div>
          <button
            v-show="!todo.isCompleted"
            @click="toggleComplete(todo.id)"
            class="py-1 px-2 bg-yellow-600 mx-1 rounded-md text-white"
          >
            完了
          </button>
          <button
            v-show="todo.isCompleted"
            @click="toggleComplete(todo.id)"
            class="py-1 px-2 bg-yellow-600 mx-1 rounded-md text-white"
          >
            未完了
          </button>
          <button
            @click="deleteTodo(todo.id)"
            class="py-1 px-2 bg-red-500 mx-1 rounded-md text-white"
          >
            削除
          </button>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, reactive } from 'vue';

const todoInput = ref('');

const todos = reactive([
  {
    id: 1,
    title: 'Task to do',
    isCompleted: true,
  },
  {
    id: 2,
    title: 'Task to do',
    isCompleted: false,
  },
]);

function deleteTodo(id) {
  todos.pop(id);
  console.log('delete');
}

function addTodo() {
  todos.push({
    id: todos.length + 1,
    title: todoInput.value,
    isCompleted: false,
  });
  todoInput.value = '';
  console.log('add');
}

function toggleComplete(id) {
  const index = todos.findIndex((todo) => todo.id === id);
  todos[index].isCompleted = !todos[index].isCompleted;
}
</script>

上記のようにデータベースに接続されていないので更新したデータはブラウザをリロードすると消えてしまいますね。

これからこのプロジェクトにFirebaseのFirestoreデータベースを連携させ、toDoの追加、編集、削除(CRUD操作)ができるようにします。

レッツゴー

リポジトリのクローン

Gitを使ったことがない人は似たようなファイルを作成しても良いですしGitHubからZipでダウンロードしてもOKです。Gitは必須ツールですので是非学んでおきましょう。Gitの使い方(YouTube)

git clone git@github.com:TraitOtaku/Vue-ToDo-Starter.git
cd Vue-ToDo-Starter
npm i 

code .
npm run dev

Firebaseのプロジェクトの作成

Firebaseのプロジェクトの作成の仕方はこちらの記事でくわしーく説明しているので先に確認しておきましょう。

Firebaseのモジュールをインストール

では下記のコマンドでFirebaseのモジュールをインストールします。

npm install firebase

次にプロジェクトの設定の歯車のアイコンの中にある全般のタブをクリックします。

そこにFirebaseのSDKのコードのスニペットがあるのでコピーしておきます。

これは大切な情報なので他人とシェアしないように!

次に、このコードを読み込ませるファイルを作成します。

src/plugins/firebase.js(名前は何でもOKです。)

// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
import { getAnalytics } from "firebase/analytics";
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries

// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
  apiKey: "AIzaSyCJCPiLJI_Z22XPE6z5Lt-kkm8Pe_9bVG8",
  authDomain: "js-hiroba.firebaseapp.com",
  projectId: "js-hiroba",
  storageBucket: "js-hiroba.appspot.com",
  messagingSenderId: "221494043726",
  appId: "1:221494043726:web:ea25e09f171d3769984f1f",
  measurementId: "G-XTRC17KCB5"
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);
const analytics = getAnalytics(app);

つぎにfirestoreにサンプルのtodosタスクを追加します。

Firestoreの設定

次にfirestoreの設定を行います。

公式ドキュメンテーションにも詳しい説明があるので読んでおきましょう。

こちらにも書いているようにimport { getFirestore } from ‘firebase/firestore’;を記載してそこからデータベースの操作を行うメソッドを活用していきます。

では下記の様にdb変数にFirestoreのデータベースを格納してエクスポートします。

これでどのコンポーネントでもfirestoreのデータベースが使えるようになります。

import { initializeApp } from 'firebase/app';
import { getFirestore } from 'firebase/firestore';
const firebaseConfig = {
  apiKey: 'AIzaSyCJCPiLJI_Z22XPE6z5Lt-kkm8Pe_9bVG8',
  authDomain: 'js-hiroba.firebaseapp.com',
  projectId: 'js-hiroba',
  storageBucket: 'js-hiroba.appspot.com',
  messagingSenderId: '221494043726',
  appId: '1:221494043726:web:ea25e09f171d3769984f1f',
  measurementId: 'G-XTRC17KCB5',
};

const app = initializeApp(firebaseConfig);

const db = getFirestore(app);

export { db };

firestoreのtodosデータを表示させる

こちらのfirebaseのドキュメンテーションを参考にしてデータベースのデータを1回読み取る機能を実装していきます。下記がFirebaseのデータベースからドキュメントを読み込む関数になります。

import { collection, getDocs } from "firebase/firestore";

const querySnapshot = await getDocs(collection(db, "cities"));
querySnapshot.forEach((doc) => {
  // doc.data() is never undefined for query doc snapshots
  console.log(doc.id, " => ", doc.data());
});

では下記のように変数todosにfirebaseから呼び出したデータを格納してみます。

const todos = ref();

onMounted(async () => {
  const querySnapshot = await getDocs(collection(db, 'todos'));
  let fbTodos = [];
  querySnapshot.forEach((doc) => {
    const todo = {
      id: doc.id,
      title: doc.data().title,
      isCompleted: doc.data().isCompleted,
    };
    // console.log(`${doc.id} => ${doc.data()}`);
    fbTodos.push(todo);
    console.log(fbTodos)
  });
  todos.value = fbTodos;
});

これで、Vueアプリからfirebaseのデータを読み込みレンダーすることができましたね。

データをリアルタイムで更新する

firebaseの強みであるリアルタイムデータベースの機能を使ってみます。

今のコードのままだとFirebaseで直接データを変更したとしてもVueアプリ側のデータは更新されません。

今回のアップデートで、複数の人が同時にデータベースを更新した際にすべてのVueアプリで表示されるデータが最新のものに更新されます。

Firebaseのリアルタイムアップデートのドキュメンテーションはこちら

参考にするコードはこちら

import { collection, query, where, onSnapshot } from "firebase/firestore";

const q = query(collection(db, "cities"), where("state", "==", "CA"));
const unsubscribe = onSnapshot(q, (querySnapshot) => {
  const cities = [];
  querySnapshot.forEach((doc) => {
      cities.push(doc.data().name);
  });
  console.log("Current cities in CA: ", cities.join(", "));
});

では下記のようにonSnapshotの関数をtodosように変更してみます。

onMounted(() => {
  onSnapshot(collection(db, 'todos'), (querySnapshot) => {
    const fbTodos = [];
    querySnapshot.forEach((doc) => {
      const todo = {
        id: doc.id,
        title: doc.data().title,
        isCompleted: doc.data().isCompleted,
      };
      fbTodos.push(todo);
    });
    todos.value = fbTodos;
  });
});

これで試しにfirestoreにデータを追加するとブラウザをリロードすることなくデータが更新されたことが分かりました。

データの変更をリッスンしているので削除と変更にも対応しています。

Firesotreにデータを追加

次にVueアプリからFriestoreにデータを追加する機能を追加します。

公式のドキュメンテーションはこちら

今回はIDをVue側で指定しないaddDocメソッドを使用します。

参考になるコードはこちら

import { collection, addDoc } from "firebase/firestore"; 

// Add a new document with a generated id.
const docRef = await addDoc(collection(db, "cities"), {
  name: "Tokyo",
  country: "Japan"
});
console.log("Document written with ID: ", docRef.id);

下記のようにコードを更新するとインプットから入力したテキストがデータベースに追加されたことが核にできますね。好みでアロー関数にしてください。

function addTodo() {
  addDoc(collection(db, 'todos'), {
    title: todoInput.value,
    isCompleted: false,
  });
  todoInput.value = '';
}

さらに先ほど設定したリアルタイムDBのおかげで自動でDBのデータが更新されましたね。

Firestoreのデータの削除

では上記と同じ流れでデータの削除をFirestoreに連携させます。

参考にするコードはこちらです。

import { doc, deleteDoc } from "firebase/firestore";

await deleteDoc(doc(db, "cities", "DC"));

ではApp.vueの削除用の関数を更新します。

import { collection, doc, onSnapshot, addDoc, deleteDoc } from 'firebase/firestore';

function deleteTodo(id) {
  deleteDoc(doc(todosCollection, id));
}

これで削除ボタンを押すとデータベースから指定したデータが削除されブラウザも更新されましたね。

データの編集

今回はtodos内のisCompleted: のキーに対する値が変更されたときにデータがアップデートされるようにしましょう。updateDocメソッドを使います。

参考にするコード

import { doc, updateDoc } from "firebase/firestore";

const washingtonRef = doc(db, "cities", "DC");

// Set the "capital" field of the city 'DC'
await updateDoc(washingtonRef, {
  capital: true
});

では下記の様にtoggleComplete関数を更新しましょう。

function toggleComplete(id) {
  const index = todos.value.findIndex((todo) => todo.id === id);
  updateDoc(doc(todosCollection, id), {
    isCompleted: !todos.value[index].isCompleted,
  });
}

これで完了ボタンを押すとデータが更新されることが確認できましたね。

データの表示順を設定

このままだとデータを追加するとランダムな場所に最後に追加したタスクが追加されてしまいます。

タスクを追加した日時間を追加して最新のものが上に追加されるように変更します。

一旦データベースのコレクションをすべて削除しておきましょう。

参考にするコード(limitは今日は使いません。)

import { query, orderBy, limit } from "firebase/firestore";  

const q = query(citiesRef, orderBy("name", "desc"), limit(3));

では下記の様に変更します。

const todosQuery = query(todosCollection, orderBy('date', 'desc'));

onMounted(() => {
  onSnapshot(todosQuery, (querySnapshot) => {
    const fbTodos = [];
    querySnapshot.forEach((doc) => {
      const todo = {
        id: doc.id,
        title: doc.data().title,
        isCompleted: doc.data().isCompleted,
        date: doc.data().date,
      };
      fbTodos.push(todo);
    });
    todos.value = fbTodos;
  });
});

これでonMount時にtodosQueryで指定したようにdateの新しい順で表示されるようになりましたね!

まとめ

いかかでしょうか?

Firebaseを使う事でこんな簡単にリアルタイムのアプリができましたね!

次回以降は、認証システムを使ってユーザーがCRUD操作をできるようなアプリを作成して

もしこの記事が役に立った場合は是非コメントをお願いします。

お疲れ様でした。