Imagine you’re building a real-time chat application where users can communicate instantly with each other, or a collaborative document editing tool similar to Google Docs.
In such applications, changes made by one user must be immediately reflected for all other users. Websockets can do that for us, but then we need to dive into managing different events that are being exchanged between two clients (for ex. A mobile application and a web app).
Ah, you got it right! Firestore, a NoSQL document database from Firebase, offers excellent real-time capabilities, and integrating it with Vue 3 can help us achieve the desired real-time functionality seamlessly.
In this blog, we’ll walk through how to configure Firestore to listen to real-time events in a Vue 3 application. We’ll explore a practical example, discuss potential applications, and provide detailed code examples to demonstrate how two different apps can listen and respond to each other’s events.
We are what we repeatedly do. Excellence, then, is not an act, but a habit. Try out Justly and start building your habits today!
Before diving into the code, let’s consider some real-world scenarios where real-time event listening is indeed needed.
These scenarios underscore the importance of real-time data synchronization, making Firestore a perfect match.
While WebSockets are a powerful technology for real-time communication, Firestore offers several advantages that make it a better choice for many applications.
I’m assuming that you’ve already set up a Firebase project and Firestore database. Let’s move toward implementation. Consider having a look at How to configure Firestore, if not.
To better understand, we will implement two different Vue apps that will listen to events from each other. On a side note, Any of them or both can be replaced with another client like a Mobile application.
Let’s create the first app Alex(For Alex) and we will create a similar second app Bob(For Bob) later.
npm install firebase
Create a firebase.js
file in the src
directory to initialize Firestore and add the code below.
P.S. Don’t forget to replace the placeholder strings with your actual Firebase configuration values.
// src/firebase.js
import { initializeApp } from 'firebase/app';
import { getFirestore } from 'firebase/firestore';
const firebaseConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "YOUR_AUTH_DOMAIN",
projectId: "YOUR_PROJECT_ID",
storageBucket: "YOUR_STORAGE_BUCKET",
messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
appId: "YOUR_APP_ID"
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
export { db };
Create collection messages
in the firestore. The document fields will be,
The collection should look like below.
Remove all the code from App.vue and routes from index.js.
<template>
<div>
<h1>Alex's device</h1>
<ul>
<li v-for="message in messages" :key="message.id">
{{ message.user }} : {{ message.text }}
</li>
</ul>
</div>
</template>
<script setup>
import {
collection,
query,
orderBy,
getDocs,
} from "firebase/firestore";
import { db } from "./firebase";
import { ref, onMounted } from "vue";
const messages = ref([]);
onMounted(async () => {
// fetches all the "messages" in ascending order of created_at
const q = query(collection(db, "messages"), orderBy("created_at"));
const querySnapshot = await getDocs(q);
messages.value = querySnapshot.docs.map((doc) => ({
id: doc.id,
text: doc.data().text,
user: doc.data().user,
}));
});
</script>
<style scoped>
ul {
list-style-type: none;
padding: 0;
}
li {
background: #f1f1f1;
margin: 5px 0;
padding: 10px;
border-radius: 5px;
}
</style>
Let’s add an input field through which the user can send a new message in chat.
Add the below code after <li></li>
. Add styles in the styles section.
<input v-model="newMessage" @keyup.enter="sendMessage" placeholder="Type a message" />
<button @click="sendMessage">Send</button>
<style>
input {
padding: 10px;
box-sizing: border-box;
}
button {
padding: 10px;
background: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
margin-left: 10px;
}
</style>
Update the <script></script>
section as below, it will manage sending and adding messages to the collection messages
.
<script setup>
import {
collection,
query,
orderBy,
addDoc,
getDocs,
} from "firebase/firestore";
import { db } from "./firebase";
import { ref, onMounted } from "vue";
const messages = ref([]); // empty list
const newMessage = ref(""); // empty new message
onMounted(async() => {
const q = query(collection(db, "messages"), orderBy("created_at"));
const querySnapshot = await getDocs(q); // fetch messages in the ascending order of created_at
messages.value = querySnapshot.docs.map(doc => ({ id: doc.id, text: doc.data().text, user: doc.data().user }));
});
// save message to "messages" collection
const sendMessage = async () => {
if (newMessage.value.trim()) {
await addDoc(collection(db, "messages"), {
text: newMessage.value,
user: "Alex",
created_at: new Date(),
});
newMessage.value = "";
}
};
</script>
Here comes a twist in the story. Until now we fetched messages and added new messages. Now we want to catch real updates on the messages collection.
That means when any document is added/updated in the messages
collection it will receive an event to update the existing data. Don’t worry! It’s not rocket science😅.
For listening events, there’s a method onSnapShot, we will replace our current getDocs method as onSnapShot can manage fetching and listening.
Refer to Firebase Firestore Guide for more details.
<template>
<div>
<h1>Alex's device</h1>
<ul>
<li v-for="message in messages" :key="message.id">
{{ message.user }} : {{ message.text }}
</li>
</ul>
<input
v-model="newMessage"
@keyup.enter="sendMessage"
placeholder="Type a message"
/>
<button @click="sendMessage">Send</button>
</div>
</template>
<script setup>
import {
collection,
query,
orderBy,
addDoc,
onSnapshot,
} from "firebase/firestore";
import { db } from "./firebase";
import { ref, onMounted } from "vue";
const messages = ref([]); // empty list
const newMessage = ref(""); // empty new message
onMounted(() => {
const q = query(collection(db, "messages"), orderBy("created_at"));
onSnapshot(q, (snapshot) => { // listen to realtime events and fetch messages
messages.value = snapshot.docs.map((doc) => ({
id: doc.id,
text: doc.data().text,
user: doc.data().user,
}));
});
});
// sends new message
const sendMessage = async () => {
if (newMessage.value.trim()) {
await addDoc(collection(db, "messages"), {
text: newMessage.value,
user: "Alex",
created_at: new Date(),
});
newMessage.value = "";
}
};
</script>
<style scoped>
ul {
list-style-type: none;
padding: 0;
}
li {
background: #f1f1f1;
margin: 5px 0;
padding: 10px;
border-radius: 5px;
}
input {
padding: 10px;
box-sizing: border-box;
}
button {
padding: 10px;
background: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
margin-left: 10px;
}
</style>
You can create the same vue app for Bob as alex, or you can create an app on other platforms too like mobile.
For now, Copy the alex app and create the same app named bob. Replace Alex with Bob everywhere in the App.vue. It’s that simple!
Let’s run both apps…
Yay! We have our chat apps handy 🎉. You will find the chat interfaces below. Let’s check if it works in real.
In this blog, we have created two web apps only, but similar can be done for web and mobile combo or two mobile apps as well.
In this blog, we went through how to set up Firestore, with the Vue project to listen to real-time events, and even create two different applications that can communicate through Firestore.
So, go ahead and start building your next real-time application with Firestore and your favorite platform. The sky’s the limit!