In this tutorial, you’ll learn how to build a fully functional real-time chat web application using Vue 3 and Firebase Realtime Database. Leveraging Vue’s Composition API and the modern modular Firebase SDK, this tutorial walks you through creating an interactive and scalable messaging app from scratch.
Whether you're a beginner looking to understand real-time data handling or an experienced developer exploring Vue 3’s capabilities, this guide will help you integrate Firebase into your Vue app efficiently using best practices.
Why Vue 3 and Firebase?
Vue 3 is a progressive JavaScript framework that offers reactive components and a simplified development experience through its Composition API. Combined with Firebase, a powerful Backend-as-a-Service (BaaS), you get real-time data sync, authentication, and hosting—all without managing a backend server.
Firebase’s Realtime Database is particularly suited for chat apps as it supports live updates, allowing users to see new messages instantly without refreshing the page.
What You’ll Build
By the end of this tutorial, you’ll have created a Vue 3 web chat application with the following features:
-
User authentication using Firebase Auth
-
Sending and receiving messages in real time
-
Automatically updating the chat UI with new messages
-
Responsive design using Vue and CSS
The flow of this Vue.js Firebase chat web app is very simple.
- When the user goes to the Vue.js app URL, it will redirect to the login page, which only has a nickname as a login field.
- After login, redirect to the select chat room page, which has a list of existing chat rooms and a button to add a new chat room.
- If you click on the add button, it will be redirected to the add-room page with a single room name field and a submit button.
- If you click on an item in the chat room list, it will be redirected to the chat page and start chatting with other users who have joined or entered the chat room.
- Chat room contains a list of messages from all users or nicknames and a button to log out of the chat room.
For more clearer explanation, you can refer to this sequence diagram.
The following tools, framework, and module are required for this tutorial:
- Node.js (Recommended version)
- Vue 3
- Vue CLI or Vite
- Google Firebase
- Terminal (Mac/Linux) or Node Command Line (Windows)
- IDE or Text Editor
We assume that you have already installed Node.js. Make sure Node.js command line is working (on Windows) or is runnable in Linux/OS X terminal.
You can watch the video tutorial on our YouTube channel.
Step 1: Set up Google Firebase Database
Next, we will set up or create a new Google Firebase project that can use the Realtime Database. Just open your browser, then go to Google Firebase Console, and you will be taken to this page.
From that page, click the "+" add project button to create a Google Firebase project then it will be redirected to this page.
After filling the project name text field, whose project name is "vue-chat-app", then click the continue button, and it will be redirected to this page.
This time, choose not to add Firebase analytics for now, then click the Create Project button.
Now, you have a Google Firebase Project ready to use.
After clicking the Continue button, it will be redirected to this page.
While developing the menu expanded in the left menu, choose "Create Database", then it will open this dialog.
Select "Start in test mode", then click next, and it will go to the next dialog.
Select the Firebase database server location (better near your Angular server location), then click the Done button. Don't forget to select or change Cloud Firestore to Realtime Database in Develop -> Database dashboard. Next, go to the Rules tab, and you will see these rule values.
{
/* Visit https://firebase.google.com/docs/database/security to learn more about security rules. */
"rules": {
".read": false,
".write": false
}
}
Change it to readable and writeable from everywhere for this tutorial only.
{
/* Visit https://firebase.google.com/docs/database/security to learn more about security rules. */
"rules": {
".read": "auth === null",
".write": "auth === null"
}
}
Click the publish button to update or save the changes. Now, the Google Firebase database is ready to use with your Vue.js chat web app.
Step 2: Create a Vue 3 Project
Option A: Vue CLI
npm install -g @vue/cli
vue create vue3-firebase-chat
# Choose “Vue 3 + Babel + Router”
Option B: Vite (recommended for speed)
npm create vite@latest vue3-firebase-chat -- --template vue
cd vue3-firebase-chat
npm install
npm install vue-router@4
To make sure that the created Vue 3 project is working, type this command to run the Vue.js application.
npm run dev
You will see this page when you open http://localhost:5173/ in the browser.
Step 3: Update Dependencies
We will use the Firebase JavaScript SDK that is available as a Node module. For that, type this command to install the Firebase module after stopping the running Vue.js app (press Ctrl + c).
npm install firebase@9 vue-router@4
Step 4: Modular Firebase Integration
In src/firebase.js
Convert to v9 modular style:
import { initializeApp } from 'firebase/app';
import { getDatabase } from 'firebase/database';
const firebaseConfig = { /* your config */ };
const app = initializeApp(firebaseConfig);
const db = getDatabase(app);
export { db };
Step 5: Rewrite Routing Using vue-router@4
Create src/router/index.js
:
import { createRouter, createWebHistory } from 'vue-router';
import Login from '../components/Login.vue';
import RoomList from '../components/Room.vue';
import Chat from '../components/Chat.vue';
const routes = [
{ path: '/', component: Login },
{ path: '/rooms', component: RoomList },
{ path: '/chat/:roomId', component: Chat, props: true },
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;
Update main.js
:
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import './firebase'; // initialize
createApp(App).use(router).mount('#app');
Step 6: Rewrite Components with Composition API
Example: src/components/Chat.vue
<template>
<div>
<h2>Room: {{ roomId }}</h2>
<div v-for="msg in messages" :key="msg.id">
<strong>{{ msg.sender }}:</strong> {{ msg.text }}
</div>
<input v-model="newMessage" @keyup.enter="send" />
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { db } from '../firebase';
import { ref as dbRef, push, onValue } from 'firebase/database';
const route = useRoute();
const roomId = route.params.roomId;
const messages = ref([]);
const newMessage = ref('');
onMounted(() => {
const r = dbRef(db, `rooms/${roomId}/messages`);
onValue(r, snapshot => {
messages.value = [];
snapshot.forEach(child => {
messages.value.push({ id: child.key, ...child.val() });
});
});
});
function send() {
if (!newMessage.value) return;
push(dbRef(db, `rooms/${roomId}/messages`), {
sender: 'Anonymous',
text: newMessage.value,
timestamp: Date.now(),
});
newMessage.value = '';
}
</script>
Example: src/components/Login.vue
<template>
<div>
<input v-model="email" placeholder="Email" />
<input v-model="password" type="password" placeholder="Password" />
<button @click="login">Login</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { getAuth, signInWithEmailAndPassword } from 'firebase/auth'
// Refs
const email = ref('')
const password = ref('')
const router = useRouter()
// Login function
const login = async () => {
try {
const auth = getAuth()
await signInWithEmailAndPassword(auth, email.value, password.value)
router.push('/chat')
} catch (err) {
alert(err.message)
}
}
</script>
Example: src/components/RoomList.vue
<template>
<div>
<ul>
<li v-for="room in rooms" :key="room.id" @click="goToRoom(room)">
{{ room.name }}
</li>
</ul>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { getDatabase, ref as dbRef, onValue } from 'firebase/database'
// Reactive state
const rooms = ref([])
const router = useRouter()
// Fetch room list on mount
onMounted(() => {
const db = getDatabase()
const roomsRef = dbRef(db, 'rooms')
onValue(roomsRef, snapshot => {
const data = snapshot.val()
rooms.value = Object.keys(data || {}).map(id => ({
id,
...data[id],
}))
})
})
// Navigate to selected room
const goToRoom = room => {
router.push(`/chat/${room.id}`)
}
</script>
Example: src/components/ChatRoom.vue
<template>
<div>
<h2>Chat Room: {{ roomName }}</h2>
<ul>
<li v-for="msg in messages" :key="msg.timestamp">
<strong>{{ msg.user }}:</strong> {{ msg.text }}
</li>
</ul>
<input
v-model="newMessage"
@keyup.enter="sendMessage"
placeholder="Type a message..."
/>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { getDatabase, ref as dbRef, push, onChildAdded } from 'firebase/database'
import { getAuth } from 'firebase/auth'
const route = useRoute()
const roomName = ref(route.params.roomId)
const messages = ref([])
const newMessage = ref('')
onMounted(() => {
const db = getDatabase()
const messagesRef = dbRef(db, `messages/${roomName.value}`)
onChildAdded(messagesRef, snapshot => {
messages.value.push(snapshot.val())
})
})
const sendMessage = () => {
const text = newMessage.value.trim()
if (!text) return
const auth = getAuth()
const user = auth.currentUser
const message = {
text,
user: user ? user.email : 'Anonymous',
timestamp: Date.now(),
}
const db = getDatabase()
const messagesRef = dbRef(db, `messages/${roomName.value}`)
push(messagesRef, message)
newMessage.value = ''
}
</script>
Example: src/components/AddRoom.vue
<template>
<div>
<h2>Create Chat Room</h2>
<input v-model="roomName" placeholder="Room name" />
<button @click="createRoom">Create</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { getDatabase, ref as dbRef, push } from 'firebase/database'
const router = useRouter()
const roomName = ref('')
const createRoom = () => {
const name = roomName.value.trim()
if (!name) return
const db = getDatabase()
const roomsRef = dbRef(db, 'rooms')
push(roomsRef, { name })
roomName.value = ''
router.push('/rooms')
}
</script>
Step 7: Styling the converted Vue 3 components
Login.vue
Styling
<template>
<div class="login">
<h2>Login</h2>
<input v-model="username" type="text" placeholder="Enter your username" />
<button @click="login">Login</button>
</div>
</template>
<style scoped>
.login {
max-width: 400px;
margin: 80px auto;
padding: 2rem;
border: 1px solid #ccc;
border-radius: 12px;
text-align: center;
}
.login h2 {
margin-bottom: 1.5rem;
}
.login input {
width: 100%;
padding: 10px;
margin-bottom: 1rem;
font-size: 16px;
border: 1px solid #aaa;
border-radius: 8px;
}
.login button {
padding: 10px 20px;
font-size: 16px;
background-color: #42b983;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
}
</style>
RoomList.vue
Styling
<template>
<div class="room-list">
<h2>Chat Rooms</h2>
<ul>
<li v-for="room in rooms" :key="room.id" @click="goToRoom(room.id)">
{{ room.name }}
</li>
</ul>
<router-link to="/add-room" class="add-room-link">+ Add Room</router-link>
</div>
</template>
<style scoped>
.room-list {
max-width: 600px;
margin: 40px auto;
padding: 1rem;
}
.room-list h2 {
text-align: center;
margin-bottom: 1rem;
}
.room-list ul {
list-style: none;
padding: 0;
}
.room-list li {
padding: 12px;
border: 1px solid #ddd;
margin-bottom: 10px;
cursor: pointer;
border-radius: 6px;
transition: background 0.2s;
}
.room-list li:hover {
background-color: #f5f5f5;
}
.add-room-link {
display: inline-block;
margin-top: 20px;
text-align: center;
color: #42b983;
text-decoration: none;
}
</style>
AddRoom.vue
Styling
<template>
<div class="add-room">
<h2>Add New Room</h2>
<input v-model="roomName" type="text" placeholder="Room name" />
<button @click="createRoom">Create Room</button>
</div>
</template>
<style scoped>
.add-room {
max-width: 400px;
margin: 80px auto;
padding: 2rem;
border: 1px solid #ccc;
border-radius: 12px;
text-align: center;
}
.add-room h2 {
margin-bottom: 1.5rem;
}
.add-room input {
width: 100%;
padding: 10px;
margin-bottom: 1rem;
font-size: 16px;
border: 1px solid #aaa;
border-radius: 8px;
}
.add-room button {
padding: 10px 20px;
font-size: 16px;
background-color: #42b983;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
}
</style>
Chat.vue
Styling
<template>
<div class="chat">
<h2>{{ roomName }}</h2>
<div class="messages">
<div
class="message"
v-for="message in messages"
:key="message.id"
>
<strong>{{ message.user }}:</strong> {{ message.text }}
</div>
</div>
<div class="send-box">
<input
v-model="newMessage"
type="text"
placeholder="Type a message"
/>
<button @click="sendMessage">Send</button>
</div>
</div>
</template>
<style scoped>
.chat {
max-width: 700px;
margin: 40px auto;
padding: 1rem;
}
.chat h2 {
text-align: center;
margin-bottom: 1rem;
}
.messages {
max-height: 400px;
overflow-y: auto;
padding: 1rem;
border: 1px solid #ddd;
border-radius: 8px;
margin-bottom: 1rem;
background-color: #fafafa;
}
.message {
padding: 6px 0;
border-bottom: 1px solid #eee;
}
.send-box {
display: flex;
gap: 10px;
}
.send-box input {
flex: 1;
padding: 10px;
font-size: 16px;
border: 1px solid #aaa;
border-radius: 8px;
}
.send-box button {
padding: 10px 20px;
font-size: 16px;
background-color: #42b983;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
}
</style>
Step 8: Run the Vue 3 Firebase Realtime Chat Web App
Running the Vue.js and Firebase real-time chat web app is very simple. Just type this command in your terminal.
npm run dev
And here it is, you can test the real-time chat web app on multiple machines or browsers. You can watch this demo to find out how it works with multiple browsers.
That's the Vue.js Firebase Realtime Chat Web App tutorial. You can check the full source code on our GitHub.
That's just the basics. If you need more deep learning about MEVN Stack, Vue.js, or related, you can take the following cheap course:
- Isomorphic JavaScript with MEVN Stack
- Fun Projects with Vue 2
- Learning Path: Vue. js: Rapid Web Development with Vue 2
- Getting Started with Vue.js 2
- Vue. js 2 Recipes
Thanks!