CRUDアプリ(Vue CLI / Firebase)の作成について
1.はじめに
*急ぎの方は下記リンクからソースコードを確認すること。
「Vue.jsとFirebaseでサクッとCRUDアプリを構築しよう」と思い、アプリ開発をしたが、エラーが発生してなかなかうまくいかず。。エラーを調査しながら進めたものの、データの表示はできるが、肝心の更新・削除・追加ができない。エラー調査中に判明したが、Firebase Javascript SDKがv8→v9へバージョンアップしたらしい。そのため、v9のコードについてあまり「やってみた系」記事が少ない模様である。
色々と記事を参考にしながら開発を進めたが、1週間経ってようやく簡易的なCRUDアプリの構築ができた。もしかしたら、v8→v9のバージョンアップにより、自分と同じように開発が滞っている人がいるのでは?と思い、記事を作成するに至った次第である。本記事では、firebaseの構築からvuejsによるCRUDアプリ開発までを解説する。
2.本記事のゴール
本記事のゴールは、firebaseとvuejsを使ったCRUDアプリを構築することがゴールである。
まず「3.Firestoreの準備」にて、firebaseでデータベースを作成する。そしてフロントエンドでデータを表示するため、作成したデータベースに対してデータを投入する。次に「4.サンプルアプリの作成」でフロントエンドの作成をする。データの表示・追加・更新・削除ができる機能を実装する。
アプリの完成イメージは下記の通り。Home画面より、shopping listのアイテムと備考を確認できる。
上部には追加ボタンがあり、追加ボタンをクリックすると新規追加の画面へ遷移する。
右側に編集ボタンと削除ボタンがあり、編集ボタンをクリックすると編集画面へ遷移する。
3.Firestoreの準備
表題の件については、下記URLに詳しく記述されているため、本記事では簡単に記載する。
まずはFirebaseのコンソール画面(https://console.firebase.google.com/) へログインすると、下記のような画面が表示される。自分の場合は、既に登録済みのプロジェクトが表示される。
そして、上記画面の「Add Project」をクリックすると下記画面が表示されるので、「My First Project」等の任意の名前をつけてプロジェクトの登録をする。
最初のFirebaseのコンソール画面(https://console.firebase.google.com/) へ戻ると、「My First Project」と表示されたプロジェクトが表示されるので、それをクリックする。
アイコンが小さくてわかりにくいが、ページ上部の「Add app」をクリックし「</>」というwebアイコンをクリックする。
下記画面へ遷移するため、任意の名前をつけてアプリを登録する。
登録後、左側の画面よりCloud Firestoreへ遷移する。
Cloud Firestoreに遷移したら、データの入力をする。今回は「shoppingLists」と言うコレクションを作成する。作成したコレクションの中で、下記の通りデータを作成する。データ作成にあたり、データタイプはstringと指定する。
item: test
note: test
最後に、アプリへデータを反映するために必要なソースコードをコピーする。まず設定画面の全般タブをクリックして、下記画面へ遷移する。
遷移後、一番下までスクロールして、赤丸で囲った部分をコピーする。
firebase側の作業は以上となる。
4.Vue-CLIの作成
4-1.初期設定
Vue CLIの作成にあたり、所定のフォルダにて下記のコマンドを実行する。
vue init webpack logistics-system
? Project name logistics-system
? Project description A Vue.js project
? Author Yosuke Ota yxo5017@icloud.com
? Vue build standalone
? Install vue-router? Yes
? Use ESLint to lint your code? No
? Set up unit tests No
? Setup e2e tests with Nightwatch? No
? Should we runnpm install
for you after the project has been created? (recom
mended) npm
Vue CLIをインストール後、ターミナルで「インストールしたフォルダ」へ移動した後に、「npm install firebase」を実行する。
また、所定のフォルダへ移動後、「npm run dev」を実行することにより、ローカルホストにてアプリの立ち上げが可能か確認する。
4-2.フォルダ構成
今回開発するアプリのフォルダ構成は下記の通り。
src/
├ components/
│ └ Navbar.vue //上部のメニュータグの作成
├ firebare/
│ └ init.js //firebaseのデータベースとの連携
├ router/
│ └ index.js //Home, Add, Edit画面へのリンク作成
├ views/
│ ├ AddItem.vue //Add画面の作成
│ ├ EditItem.vue //Edit画面の作成
│ └ index.vue //Home画面の作成
├ App.vue
└ main.js
4-2-1.init.js
init.js画面では、下記の通り必要なfirebaseモジュールをimportして、こちらのファイルをdbとしてexportしている。その結果、他のコンポーネントにて、データベースの操作が可能となる。
import { initializeApp } from "firebase/app";
import { getFirestore } from 'firebase/firestore';
// 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: "******",
authDomain: "******",
projectId: "******",
storageBucket: "******",
messagingSenderId: "******",
appId: "******",
measurementId: "******"
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
export default db
4-2-2.router.js
まず、必要なコンポーネント(Vue, Router, Index, AddItem, EditItem)をimportしている。その上で、new Routerにて、各種ページへのリンクを作成している。
import Vue from 'vue'
import Router from 'vue-router'
import Index from '../views/Index'
import AddItem from '../views/AddItem.vue'
import EditItem from '../views/EditItem.vue'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'Index',
component: Index
},
{
path: '/additem',
name: 'AddItem',
component: AddItem
},
{
path: '/edititem/:item_slug',
name: 'EditItem',
component: EditIng
}
]
})
4-2-3.Navbar
Navbarは、<router-link to="/">より、Home画面へ戻れるようにリンクを設定している。また、<router-link to="/additem">より、Add画面へ遷移可能である。
<template>
<div class="navbar">
<nav class="nav-extended indigo darken-2">
<div class="nav-content">
<router-link to="/">
<span class="nav-title">Shopping List</span>
</router-link>
<a href="" class="btn-floating btn-large halfway-fab pink">
<router-link to="/additem">
<i class="material-icons">add</i>
</router-link>
</a>
</div>
</nav>
</div>
</template>
4-2-4.AddItem
<form @submit.prevent="addItem">により、アイテムの追加をする関数が実行される。
<template>
<div class="add-item container">
<form @submit.prevent="addItem">
<br>
<div class="field title">
<label for="title">Item</label>
<input type="text" name="item" v-model="item">
</div>
<div class="field title">
<label for="title">Note</label>
<input type="text" name="note" v-model="note">
</div>
<div class="field center-align">
<button class="btn pink">Add</button>
</div>
</form>
</div>
</template>
<script>
import {doc, collection, getDocs, onSnapshot, addDoc, query, orderBy, deleteDoc, setDoc} from "firebase/firestore";
import db from '@/firebase/init'
export default {
name: 'AddItem',
data () {
return {
items: [],
item: null,
note: null,
}
},
実行される関数は、フォーム画面のアイテムと備考が記載されていないと実行されない。条件を満たしていると実行され、まずはローカルのitemsへ格納される。次に、addDocでfirebaseのshoppingListsというDBを取得して、こちらへデータが格納される。データが格納されたら、Index画面へ遷移する。
methods: {
addItem(){
if(this.item && this.note){
this.items.push([this.item, this.note])
addDoc(collection(db, "shoppingLists"), {
item: this.item,
note: this.note,
timestamp: new Date()
}).then(()=> {
this.$router.push({ name: 'Index' })
}).catch(err => console.error(err))
this.item = null;
this.note = null;
}
}
}
}
4-2-5.EditItem
編集画面で更新ボタンを押すと、updateItem関数が実行される。編集画面のitemおよびnote項目には、index.vueより渡された、itemおよびnoteが表示される。
<template>
<div class="add-item container">
<form @submit.prevent="updateItem">
<br>
<div class="field title">
<label for="title">Item</label>
<input type="text" name="item" v-model="item">
</div>
<div class="field title">
<label for="title">Note</label>
<input type="text" name="note" v-model="note">
</div>
<div class="field center-align">
<button class="btn pink">Update</button>
</div>
</form>
</div>
</template>
上記で説明した通り、index.vueより渡された、itemおよびnoteが表示されるようにするため、returnにてデータ表示されるように$route.paramsの各種項目の設定がされている。
methodsにてupdateItem関数が設定されている。
- let itemRefで更新するDBの設定をしている
- setDocにて、更新する項目の設定をしている。例えばDBのitemはthis.itemで更新するなど
- 更新後は、Home画面へ遷移する。
<script>
import {doc, collection, getDocs, onSnapshot, addDoc, query, orderBy, deleteDoc, setDoc} from "firebase/firestore";
import db from '@/firebase/init'
export default {
name: 'EditItem',
data () {
return {
pass: this.$route.params.item_slug,
item: this.$route.params.item,
note: this.$route.params.note
}
},
methods: {
async updateItem() {
let itemRef = doc(db, "shoppingLists", this.pass);
setDoc(itemRef, {
item: this.item,
note: this.note,
timestamp: new Date()
}).then(()=> {
this.$router.push({ name: 'Index' })
}).catch(err => console.error(err))
this.item = null;
this.note = null;
},
},
created(){
}
}
</script>
<style>
.add-item{
padding: 10px;
}
</style>
5-2-6.index
- <table>タグにより、データをテーブル表示している。
- 表示するデータについては、this.itemsより取得している。
- edit/deleteボタンについては、それぞれクリックされたら関数が実行されるように設定している
- editについては、item_slug, item, noteをEditItem画面へ渡すように設定している。
<template>
<div class="add-item container">
<br>
<table>
<thead>
<tr>
<th>No.</th>
<th>Item</th>
<th>Note</th>
</tr>
</thead>
<tbody v-for="(item, index) in this.items" :key="item[2]">
<td>{{ index + 1 }}</td>
<td>{{ item[0] }}</td>
<td>{{ item[1] }}</td>
<router-link :to="{ name: 'EditItem', params: {item_slug: item[2], item: item[0], note: item[1]}}">
<i class="material-icons edit">edit</i>
</router-link>
<i class="material-icons delete" @click="deleteItem(item[2])">delete</i>
</tbody>
</table>
</div>
</template>
<script src="https://www.gstatic.com/firebasejs/9.1.0/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/9.1.0/firebase-firestore.js"></script>
- deleteItem関数は、deleteDocによりデータを削除している。その後、filterによりローカル側でもデータを削除している。
- getItemsはテーブルへデータ表示させるための関数。データ表示をするため、this.itemsへデータを格納している。
- また、アプリが立ち上がったときにgetItemsが実行されるように、createdで関数を実行している。
<script>
import {doc, collection, getDocs, onSnapshot, addDoc, query, orderBy, deleteDoc, setDoc} from "firebase/firestore";
import db from '@/firebase/init'
export default {
name: 'Index',
data () {
return {
items: [],
item: null,
note: null
}
},
methods: {
async deleteItem(id){
let request = await deleteDoc(doc(db, "shoppingLists", id));
this.items = this.items.filter(item => {
return item[2] != id
})
},
async getItems(db) {
const querySnapshot = await getDocs(collection(db, "shoppingLists"));
querySnapshot.forEach((doc) => {
this.items.push([doc.data().item, doc.data().note, doc.id]);
});
}
},
created(){
this.getItems(db);
}
}
</script>
<style>
.add-item{
padding: 20px;
max-width: 500px;
}
</style>
5.終わりに
今回はfirebaseとvuejsを使った簡単なアプリを構築した。今後も別のフレームワークを使用した簡単なCRUDアプリについて投稿する予定なので、何かリクエストがあればコメントをいただきたい。
Discussion