Vue Firebase Realtime Database: CRUD example
Vue Firebase CRUD with Realtime Database Overview
We’re gonna build an Vue Firebase CRUD App using firebase library in which:
- Each Tutorial has key, title, description, published status.
- We can create, retrieve, update, delete Tutorials (CRUD operations) from Firebase Realtime Database
Here are the screenshots:
– Create a new Tutorial:
Firebase Realtime Database right after the Operation:
– Retrieve all Tutorials with details when clicking on a Tutorial:
– Change status to Published/Pending using Publish/UnPublish button:
– Update the Tutorial details with Update button
– Delete all Tutorials with Remove All button:
CRUD Operations using firebase Reference
We’re gonna use instance of firebase.database.Reference to read/write data from the Firebase database.
var tutorialsRef = firebase.database().ref("/tutorials");
– Read list once using once()
:
tutorialsRef.once('value', function(snapshot) {
vat tutorials = [];
snapshot.forEach(function(childSnapshot) {
var key = childSnapshot.key;
var data = childSnapshot.val();
// ...
tutorials.push({ key: key, title: data.title, description: data.description});
});
});
– Read List with listening to the data changes using on()
:
tutorialsRef.on('child_added', function(data) {
// data.key, data.val().title, data.val().description
});
tutorialsRef.on('child_changed', function(data) {
// data.key, data.val().title, data.val().description
});
tutorialsRef.on('child_removed', function(data) {
// data.key, data.val().title, data.val().description
});
– Listening for all value events on a List reference
var onDataChange =tutorialsRef.on('value', function(snapshot) {
snapshot.forEach(function(childSnapshot) {
var childKey = childSnapshot.key;
var childData = childSnapshot.val();
// ...
});
});
– Remove the listener using off()
:
tutorialsRef.off("value", onDataChange);
– Create a new object in List:
tutorialsRef.push({
title: "bezkoder Tut#1",
description: "Helpful tutorial"
});
– Update object in List:
+ destructive update using set()
: delete everything currently in place, then save the new value
tutorialsRef.child(key).set({
title: 'zkoder Tut#1',
description: 'Tut#1 Description'
});
+ non-destructive update using update()
: only updates the specified values
tutorialsRef.child(key).update({
title: 'zkoder new Tut#1'
});
– Delete an object in List:
tutorialsRef.child(key).remove();
– Delete entire List:
tutorialsRef.remove();
Technology
- Vue 2
- Vue Router 3
- Firebase 7
- Bootstrap 4
Setup the Firebase Project
Go to Firebase Console, login with your Google Account, then click on Add Project.
You will see the window like this:
Enter Project name, set Project Id and click on Continue.
Turn off Enable Google Analytics for this project, then click Create Project.
Now, browser turns into following view:
If you don’t see it, just choose Project Overview.
Click on Web App, you will see:
Set the nickname and choose Register App for next step.
Copy the script for later use.
Choose Database in the left (list of Firebase features) -> Realtime Database -> Create Database.
In this tutorial, we don’t implement Authentication, so let’s choose test mode:
Or if you come from another situation, just open Tab Rules, then change .read
and .write
values to true
.
Vue Project Structure
Let me explain it briefly.
– package.json contains 3 main modules: vue
, vue-router
, firebase
.
– firebase.js
configures information to connect with Firebase Project and export Firebase Database service.
– TutorialDataService
exports TutorialDataService
that uses firebase
‘s Database Reference
to interact with Firebase Database.
– There are 3 components that uses TutorialDataService
:
AddTutorial
for creating new itemTutorialsList
contains list of items, parent ofTutorial
Tutorial
shows item details
– router.js defines routes for components.
– App.Vue
contains Router View and navigation bar.
Setup Vue.js Project
Open cmd at the folder you want to save Project folder, run command:vue create vue-firebase-crud
You will see some options, choose default (babel, eslint).
After the process is done. We create new folders and files like the following tree:
public
index.html
src
components
AddTutorial.vue
Tutorial.vue
TutorialsList.vue
services
TutorialDataService.js
App.vue
firebase.js
main.js
package.json
Open public/index.html, add bootstrap inside <head>
tag:
Add Vue Router to Vue Firebase CRUD App
– Run the command: npm install vue-router
.
– In src folder, create router.js and define Router
as following code:
import Vue from "vue";
import Router from "vue-router";
Vue.use(Router);
export default new Router({
mode: "history",
routes: [
{
path: "/",
alias: "/tutorials",
name: "tutorials",
component: () => import("./components/TutorialsList")
},
{
path: "/add",
name: "add",
component: () => import("./components/AddTutorial")
}
]
});
– Open src/main.js, then import router
:
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App),
}).$mount('#app')
Let’s open src/App.vue, this App
component is the root container for our application, it will contain a navbar
and router-view
.
There are 2 main routes:
/add
forAddTutorial
component/tutorials
forTutorialsList
component
<template>
<div id="app">
<nav class="navbar navbar-expand navbar-dark bg-dark">
<router-link to="/" class="navbar-brand">bezKoder</router-link>
<div class="navbar-nav mr-auto">
<li class="nav-item">
<router-link to="/tutorials" class="nav-link">Tutorials</router-link>
</li>
<li class="nav-item">
<router-link to="/add" class="nav-link">Add</router-link>
</li>
</div>
</nav>
<div class="container mt-3">
<h2>Vue Firebase Realtime Database CRUD</h2>
<router-view />
</div>
</div>
</template>
<script>
export default {
name: "app"
};
</script>
<style scoped>
.container h2 {
text-align: center;
margin: 25px auto;
}
</style>
Integrate Firebase into Vue App
First run the command: npm install firebase
.
Open src/firebase.js, import firebase
library and add configuration that we have saved when Popup window was shown:
import * as firebase from "firebase";
import "firebase/database";
let config = {
apiKey: "xxx",
authDomain: "bezkoder-firebase.firebaseapp.com",
databaseURL: "https://bezkoder-firebase.firebaseio.com",
projectId: "bezkoder-firebase",
storageBucket: "bezkoder-firebase.appspot.com",
messagingSenderId: "xxx",
appId: "xxx",
};
firebase.initializeApp(config);
export default firebase.database();
Don’t forget to export firebase.database.Database
service with firebase.database()
.
Create Data Service
This service will use Firebase Database
service to interact with Firebase Realtime Database. It contains necessary functions for CRUD operations.
services/TutorialDataService.js
import firebase from "../firebase";
const db = firebase.ref("/tutorials");
class TutorialDataService {
getAll() {
return db;
}
create(tutorial) {
return db.push(tutorial);
}
update(key, value) {
return db.child(key).update(value);
}
delete(key) {
return db.child(key).remove();
}
deleteAll() {
return db.remove();
}
}
export default new TutorialDataService();
Component for creating Object
This component has a Form to submit new Tutorial with 3 fields: title
, description
& published
(false by default). It calls TutorialDataService.create()
method.
components/AddTutorial.vue
<template>
...
</template>
<script>
import TutorialDataService from "../services/TutorialDataService";
export default {
name: "add-tutorial",
data() {
return {
tutorial: {
title: "",
description: "",
published: false
},
submitted: false
};
},
methods: {
saveTutorial() {
var data = {
title: this.tutorial.title,
description: this.tutorial.description,
published: false
};
TutorialDataService.create(data)
.then(() => {
console.log("Created new item successfully!");
this.submitted = true;
})
.catch(e => {
console.log(e);
});
},
newTutorial() {
this.submitted = false;
this.tutorial = {
title: "",
description: "",
published: false
};
}
}
};
</script>
There are 2 main variables return from data()
:
– tutorial
for tutorial data
– submitted
status
We also have a function to get value of the form (state) and call TutorialDataService.create()
method.
For the template, we check the submitted
state, if it is true, we show Add button for creating new Tutorial again. Otherwise, a Form will display.
<template>
<div class="submit-form">
<div v-if="!submitted">
<div class="form-group">
<label for="title">Title</label>
<input
type="text"
class="form-control"
id="title"
required
v-model="tutorial.title"
name="title"
/>
</div>
<div class="form-group">
<label for="description">Description</label>
<input
class="form-control"
id="description"
required
v-model="tutorial.description"
name="description"
/>
</div>
<button @click="saveTutorial" class="btn btn-success">Submit</button>
</div>
<div v-else>
<h4>You submitted successfully!</h4>
<button class="btn btn-success" @click="newTutorial">Add</button>
</div>
</div>
</template>
<script>
import TutorialDataService from "../services/TutorialDataService";
export default {
name: "add-tutorial",
...
};
</script>
Component for List of Objects
This component has:
- a tutorials array displayed as a list on the left.
- a selected Tutorial which is shown on the right.
So we will have following state:
tutorials
currentTutorial
andcurrentIndex
We also need to use 2 TutorialDataService
methods:
getAll()
deleteAll()
And add Tutorial
into this component as child component.
components/TutorialsList.Vue
<template>
...
</template>
<script>
import TutorialDataService from "../services/TutorialDataService";
import TutorialDetails from "./Tutorial";
export default {
name: "tutorials-list",
components: { TutorialDetails },
data() {
return {
tutorials: [],
currentTutorial: null,
currentIndex: -1
};
},
methods: {
onDataChange(items) {
let _tutorials = [];
items.forEach((item) => {
let key = item.key;
let data = item.val();
_tutorials.push({
key: key,
title: data.title,
description: data.description,
published: data.published,
});
});
this.tutorials = _tutorials;
},
refreshList() {
this.currentTutorial = null;
this.currentIndex = -1;
},
setActiveTutorial(tutorial, index) {
this.currentTutorial = tutorial;
this.currentIndex = index;
},
removeAllTutorials() {
TutorialDataService.deleteAll()
.then(() => {
this.refreshList();
})
.catch((e) => {
console.log(e);
});
},
},
mounted() {
TutorialDataService.getAll().on("value", this.onDataChange);
},
beforeDestroy() {
TutorialDataService.getAll().off("value", this.onDataChange);
}
};
</script>
In the code above, we add a listener for data value changes in mounted()
and detach the listener in beforeDestroy()
.
Inside listener function, we get the key
and other fields of each item. This key
is unique and important for update operation.
We also have refreshList()
function for every time delete operation is done.
Let’s continue to implement the template:
<template>
<div class="list row">
<div class="col-md-6">
<h4>Tutorials List</h4>
<ul class="list-group">
<li
class="list-group-item"
:class="{ active: index == currentIndex }"
v-for="(tutorial, index) in tutorials"
:key="index"
@click="setActiveTutorial(tutorial, index)"
>
{{ tutorial.title }}
</li>
</ul>
<button class="m-3 btn btn-sm btn-danger" @click="removeAllTutorials">
Remove All
</button>
</div>
<div class="col-md-6">
<div v-if="currentTutorial">
<tutorial-details
:tutorial="currentTutorial"
@refreshList="refreshList"
/>
</div>
<div v-else>
<br />
<p>Please click on a Tutorial...</p>
</div>
</div>
</div>
</template>
<script>...
import TutorialDetails from "./Tutorial";
export default {
name: "tutorials-list",
components: { TutorialDetails },
...
};
</script>
<style>
.list {
text-align: left;
max-width: 750px;
margin: auto;
}
</style>
You can see that when we click on any item, setActiveTutorial()
function will be invoked to change current active Tutorial, which data is passed to Tutorial
component.
Component for Object details
This component is the child of TutorialsList
component. It bind tutorial
data and invoke refreshList
of the parent.
For getting update, delete the Tutorial, we’re gonna use two TutorialDataService
methods:
update()
delete()
components/Tutorial.vue
<template>
...
</template>
<script>
import TutorialDataService from "../services/TutorialDataService";
export default {
name: "tutorial",
props: ["tutorial"],
data() {
return {
currentTutorial: null,
message: "",
};
},
watch: {
tutorial: function(tutorial) {
this.currentTutorial = { ...tutorial };
this.message = "";
},
},
methods: {
updatePublished(status) {
TutorialDataService.update(this.currentTutorial.key, {
published: status,
})
.then(() => {
this.currentTutorial.published = status;
this.message = "The status was updated successfully!";
})
.catch((e) => {
console.log(e);
});
},
updateTutorial() {
const data = {
title: this.currentTutorial.title,
description: this.currentTutorial.description,
};
TutorialDataService.update(this.currentTutorial.key, data)
.then(() => {
this.message = "The tutorial was updated successfully!";
})
.catch((e) => {
console.log(e);
});
},
deleteTutorial() {
TutorialDataService.delete(this.currentTutorial.key)
.then(() => {
this.$emit("refreshList");
})
.catch((e) => {
console.log(e);
});
},
},
mounted() {
this.message = "";
this.currentTutorial = { ...this.tutorial }
},
};
</script>
And this is the code for template:
<template>
<div v-if="currentTutorial" class="edit-form">
<h4>Tutorial</h4>
<form>
<div class="form-group">
<label for="title">Title</label>
<input
type="text"
class="form-control"
id="title"
v-model="currentTutorial.title"
/>
</div>
<div class="form-group">
<label for="description">Description</label>
<input
type="text"
class="form-control"
id="description"
v-model="currentTutorial.description"
/>
</div>
<div class="form-group">
<label><strong>Status:</strong></label>
{{ currentTutorial.published ? "Published" : "Pending" }}
</div>
</form>
<button
class="badge badge-primary mr-2"
v-if="currentTutorial.published"
@click="updatePublished(false)"
>
UnPublish
</button>
<button
v-else
class="badge badge-primary mr-2"
@click="updatePublished(true)"
>
Publish
</button>
<button class="badge badge-danger mr-2" @click="deleteTutorial">
Delete
</button>
<button type="submit" class="badge badge-success" @click="updateTutorial">
Update
</button>
<p>{{ message }}</p>
</div>
<div v-else>
<br />
<p>Please click on a Tutorial...</p>
</div>
</template>
<script>...
export default {
name: "tutorial",
props: ["tutorial"],
...
};
</script>
<style>
.edit-form {
max-width: 300px;
margin: auto;
}
</style>
Run & Check
You can run this App with command: npm run serve
.
DONE Compiled successfully!
App running at:
- Local: http://localhost:8080/
- Network: http://192.168.1.7:8080/
Note that the development build is not optimized.
To create a production build, run npm run build.
Open browser with url: http://localhost:8080/
and check the result.
Conclusion
Today we’ve built Vue Firebase Realtime Database example with a CRUD Application successfully using firebase
library. Now we can display, modify, delete object and list at ease.
If you want to use Firestore instead:
Vue Firestore: Build a CRUD App example
You can also find how to create Vue HTTP Client for working with Restful API in:
Vue.js 2 CRUD Application with Vue Router & Axios
Happy learning, see you again!