javascriptroom guide

Creating a Real-Time Chat App with Vue.js and Firebase

Real-time communication has become a cornerstone of modern web applications, powering everything from social media platforms to collaborative tools. If you’re looking to build a real-time chat app, Vue.js and Firebase make an excellent stack: Vue.js provides a reactive, component-based frontend framework, while Firebase offers a serverless backend with built-in real-time database (Firestore) and authentication services. In this tutorial, we’ll walk through building a fully functional real-time chat application. You’ll learn how to set up Vue.js, integrate Firebase for authentication and real-time data, and deploy the app. By the end, you’ll have a chat app where users can register, log in, and send messages that update instantly across devices.

Table of Contents

  1. Prerequisites
  2. Setting Up the Vue.js Project
  3. Setting Up Firebase
  4. Implementing User Authentication
  5. Building the Chat Interface
  6. Real-Time Message Handling with Firestore
  7. Testing the Application
  8. Deploying the App with Firebase Hosting
  9. Enhancements and Next Steps
  10. Conclusion
  11. References

Prerequisites

Before starting, ensure you have the following tools and accounts:

  • Node.js (v14+ recommended) and npm (or Yarn) installed.
  • Vue CLI: Install globally with npm install -g @vue/cli.
  • A Firebase account: Sign up for free at firebase.google.com.
  • Basic knowledge of Vue.js (components, props, reactivity) and JavaScript.

Setting Up the Vue.js Project

First, let’s create a new Vue project. Open your terminal and run:

vue create realtime-chat-app  

When prompted, select Manually select features and choose:

  • Babel
  • Vuex (for state management)
  • Vue Router (for navigation)
  • CSS Pre-processors (optional, but useful for styling)

Once the project is created, navigate into the directory and install the Firebase SDK (required to interact with Firebase services):

cd realtime-chat-app  
npm install firebase  

Setting Up Firebase

Step 1: Create a Firebase Project

  1. Go to the Firebase Console and click Add project.
  2. Enter a project name (e.g., “realtime-chat-app”) and follow the prompts to create the project.

Step 2: Register Your App

  1. In the Firebase Console, click the Web icon (</>) to register a web app.
  2. Enter an app nickname (e.g., “chat-app-client”) and click Register app.
  3. Copy the Firebase configuration snippet (it looks like this):
const firebaseConfig = {  
  apiKey: "YOUR_API_KEY",  
  authDomain: "YOUR_AUTH_DOMAIN",  
  projectId: "YOUR_PROJECT_ID",  
  storageBucket: "YOUR_STORAGE_BUCKET",  
  messagingSenderId: "YOUR_SENDER_ID",  
  appId: "YOUR_APP_ID"  
};  

Step 3: Enable Services

We’ll use two core Firebase services:

  • Authentication: To let users register and log in.
  • Cloud Firestore: A NoSQL database for real-time message storage.

Enable Authentication

  1. In the Firebase Console, go to Authentication > Sign-in method.
  2. Enable Email/Password as a sign-in provider.

Enable Cloud Firestore

  1. Go to Firestore Database > Create database.
  2. Select Start in test mode (for development; we’ll secure it later) and click Next.
  3. Choose a region (e.g., “us-central1”) and click Enable.

Step 4: Initialize Firebase in Vue

Create a Firebase configuration file in your Vue project. Create src/firebase.js and paste the config snippet from Step 2:

// src/firebase.js  
import { initializeApp } from "firebase/app";  
import { getAuth } from "firebase/auth";  
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_SENDER_ID",  
  appId: "YOUR_APP_ID"  
};  

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

// Export services for use in the app  
export const auth = getAuth(app);  
export const db = getFirestore(app);  

Replace the placeholder values with your actual Firebase config.

Implementing User Authentication

We’ll build login and registration pages so users can authenticate.

Step 1: Set Up Routes

Update src/router/index.js to define routes for login, registration, and the chat page:

// src/router/index.js  
import { createRouter, createWebHistory } from 'vue-router';  
import Login from '../views/Login.vue';  
import Register from '../views/Register.vue';  
import Chat from '../views/Chat.vue';  
import { auth } from '../firebase';  

// Protect routes: redirect unauthenticated users to login  
const requireAuth = (to, from, next) => {  
  if (!auth.currentUser) {  
    next({ name: 'Login' });  
  } else {  
    next();  
  }  
};  

const routes = [  
  {  
    path: '/',  
    name: 'Login',  
    component: Login  
  },  
  {  
    path: '/register',  
    name: 'Register',  
    component: Register  
  },  
  {  
    path: '/chat',  
    name: 'Chat',  
    component: Chat,  
    beforeEnter: requireAuth // Protect this route  
  }  
];  

const router = createRouter({  
  history: createWebHistory(),  
  routes  
});  

export default router;  

Step 2: Build Login and Registration Components

Register Component (src/views/Register.vue)

This component lets users create an account with email and password:

<template>  
  <div class="auth-container">  
    <h2>Register</h2>  
    <form @submit.prevent="register">  
      <div class="input-group">  
        <label>Email</label>  
        <input type="email" v-model="email" required>  
      </div>  
      <div class="input-group">  
        <label>Password</label>  
        <input type="password" v-model="password" required minlength="6">  
      </div>  
      <button type="submit">Register</button>  
      <p>Already have an account? <router-link to="/">Login</router-link></p>  
      <p v-if="error" class="error">{{ error }}</p>  
    </form>  
  </div>  
</template>  

<script>  
import { ref } from 'vue';  
import { createUserWithEmailAndPassword } from 'firebase/auth';  
import { auth } from '../firebase';  
import { useRouter } from 'vue-router';  

export default {  
  setup() {  
    const email = ref('');  
    const password = ref('');  
    const error = ref('');  
    const router = useRouter();  

    const register = async () => {  
      try {  
        await createUserWithEmailAndPassword(auth, email.value, password.value);  
        router.push('/chat'); // Redirect to chat after registration  
      } catch (err) {  
        error.value = err.message;  
      }  
    };  

    return { email, password, error, register };  
  }  
};  
</script>  

<style scoped>  
/* Add basic styling here */  
.auth-container { max-width: 400px; margin: 2rem auto; padding: 2rem; border: 1px solid #ddd; border-radius: 8px; }  
.input-group { margin-bottom: 1rem; }  
input { width: 100%; padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px; }  
button { width: 100%; padding: 0.5rem; background: #2c3e50; color: white; border: none; border-radius: 4px; cursor: pointer; }  
.error { color: red; }  
</style>  

Login Component (src/views/Login.vue)

Similar to registration, but uses signInWithEmailAndPassword:

<template>  
  <div class="auth-container">  
    <h2>Login</h2>  
    <form @submit.prevent="login">  
      <div class="input-group">  
        <label>Email</label>  
        <input type="email" v-model="email" required>  
      </div>  
      <div class="input-group">  
        <label>Password</label>  
        <input type="password" v-model="password" required>  
      </div>  
      <button type="submit">Login</button>  
      <p>Don't have an account? <router-link to="/register">Register</router-link></p>  
      <p v-if="error" class="error">{{ error }}</p>  
    </form>  
  </div>  
</template>  

<script>  
import { ref } from 'vue';  
import { signInWithEmailAndPassword } from 'firebase/auth';  
import { auth } from '../firebase';  
import { useRouter } from 'vue-router';  

export default {  
  setup() {  
    const email = ref('');  
    const password = ref('');  
    const error = ref('');  
    const router = useRouter();  

    const login = async () => {  
      try {  
        await signInWithEmailAndPassword(auth, email.value, password.value);  
        router.push('/chat'); // Redirect to chat after login  
      } catch (err) {  
        error.value = err.message;  
      }  
    };  

    return { email, password, error, login };  
  }  
};  
</script>  

<style scoped>  
/* Reuse auth-container styling from Register.vue */  
</style>  

Building the Chat Interface

Now, let’s create the chat page where users send and receive messages.

Chat View (src/views/Chat.vue)

This component includes:

  • A logout button.
  • A list of messages.
  • An input field to send new messages.
<template>  
  <div class="chat-container">  
    <div class="chat-header">  
      <h2>Real-Time Chat</h2>  
      <button @click="logout">Logout</button>  
    </div>  

    <div class="messages">  
      <div v-for="message in messages" :key="message.id" class="message">  
        <div class="message-sender">{{ message.sender }}</div>  
        <div class="message-text">{{ message.text }}</div>  
        <div class="message-time">{{ formatTime(message.timestamp) }}</div>  
      </div>  
    </div>  

    <div class="message-input">  
      <input  
        type="text"  
        v-model="newMessage"  
        @keyup.enter="sendMessage"  
        placeholder="Type a message..."  
      >  
      <button @click="sendMessage">Send</button>  
    </div>  
  </div>  
</template>  

<script>  
import { ref, onMounted, onUnmounted, computed } from 'vue';  
import { signOut } from 'firebase/auth';  
import { collection, addDoc, serverTimestamp, query, orderBy, onSnapshot } from 'firebase/firestore';  
import { auth, db } from '../firebase';  
import { useRouter } from 'vue-router';  

export default {  
  setup() {  
    const newMessage = ref('');  
    const messages = ref([]);  
    const router = useRouter();  
    let unsubscribe; // To stop listening to Firestore updates  

    // Get current user (from Firebase Auth)  
    const user = computed(() => auth.currentUser);  

    // Format timestamp (e.g., "3:45 PM")  
    const formatTime = (timestamp) => {  
      return new Date(timestamp?.toDate()).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });  
    };  

    // Fetch messages in real-time  
    const fetchMessages = () => {  
      const q = query(  
        collection(db, 'messages'),  
        orderBy('timestamp', 'asc') // Sort messages by time  
      );  

      // Listen for real-time updates  
      unsubscribe = onSnapshot(q, (snapshot) => {  
        messages.value = snapshot.docs.map(doc => ({  
          id: doc.id,  
          ...doc.data()  
        }));  
      });  
    };  

    // Send a new message to Firestore  
    const sendMessage = async () => {  
      if (newMessage.value.trim()) {  
        try {  
          await addDoc(collection(db, 'messages'), {  
            text: newMessage.value,  
            sender: user.value.email.split('@')[0], // Use email prefix as display name  
            userId: user.value.uid,  
            timestamp: serverTimestamp() // Auto-generate timestamp  
          });  
          newMessage.value = ''; // Clear input  
        } catch (error) {  
          console.error('Error sending message:', error);  
        }  
      }  
    };  

    // Logout user  
    const logout = async () => {  
      await signOut(auth);  
      router.push('/');  
    };  

    // Start listening to messages when component mounts  
    onMounted(fetchMessages);  

    // Stop listening when component unmounts (cleanup)  
    onUnmounted(() => unsubscribe());  

    return {  
      newMessage,  
      messages,  
      sendMessage,  
      logout,  
      formatTime  
    };  
  }  
};  
</script>  

<style scoped>  
.chat-container {  
  max-width: 800px;  
  margin: 0 auto;  
  height: 100vh;  
  display: flex;  
  flex-direction: column;  
}  

.chat-header {  
  padding: 1rem;  
  background: #2c3e50;  
  color: white;  
  display: flex;  
  justify-content: space-between;  
  align-items: center;  
}  

.messages {  
  flex: 1;  
  padding: 1rem;  
  overflow-y: auto;  
  background: #f5f5f5;  
}  

.message {  
  background: white;  
  padding: 0.5rem;  
  margin-bottom: 1rem;  
  border-radius: 8px;  
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);  
}  

.message-sender {  
  font-weight: bold;  
  color: #2c3e50;  
}  

.message-time {  
  font-size: 0.8rem;  
  color: #777;  
  text-align: right;  
}  

.message-input {  
  display: flex;  
  padding: 1rem;  
  background: white;  
  border-top: 1px solid #ddd;  
}  

.message-input input {  
  flex: 1;  
  padding: 0.5rem;  
  border: 1px solid #ddd;  
  border-radius: 4px;  
  margin-right: 0.5rem;  
}  

.message-input button {  
  padding: 0.5rem 1rem;  
  background: #3498db;  
  color: white;  
  border: none;  
  border-radius: 4px;  
  cursor: pointer;  
}  
</style>  

Real-Time Message Handling with Firestore

How It Works:

  • Sending Messages: When the user clicks “Send” or presses Enter, sendMessage adds a new document to the messages Firestore collection with text, sender, userId, and timestamp.
  • Real-Time Updates: onSnapshot listens for changes to the messages collection. When a new message is added, Firestore immediately notifies the app, and messages.value updates, triggering a re-render in Vue.

Firestore Security Rules

By default, Firestore blocks all database access. To allow authenticated users to read/write messages, update your Firestore security rules:

  1. Go to Firestore Database > Rules in the Firebase Console.
  2. Replace the default rules with:
rules_version = '2';  
service cloud.firestore {  
  match /databases/{database}/documents {  
    match /messages/{message} {  
      // Allow read/write only if user is authenticated  
      allow read, write: if request.auth != null;  
    }  
  }  
}  

Click Publish to save the rules.

Testing the Application

  1. Start the Vue development server:
npm run serve  
  1. Open http://localhost:8080 in your browser.
  2. Register a new user (e.g., [email protected] with password password123).
  3. Log in, then open a second browser tab (or incognito window) and register another user (e.g., [email protected]).
  4. Send messages from both tabs—you’ll see them update in real-time!

Deploying the App with Firebase Hosting

Firebase Hosting makes it easy to deploy Vue apps. Here’s how:

Step 1: Build the Vue App

First, create a production build of your Vue app:

npm run build  

This generates a dist folder with optimized assets.

Step 2: Deploy with Firebase Hosting

  1. Install the Firebase CLI (if not already installed):
npm install -g firebase-tools  
  1. Log in to Firebase:
firebase login  
  1. Initialize Firebase in your project:
firebase init  
  • Select Hosting from the list of services.
  • Choose your Firebase project (the one you created earlier).
  • Set the public directory to dist (the folder with your build files).
  • Answer “Yes” to “Configure as a single-page app” (since Vue uses client-side routing).
  1. Deploy the app:
firebase deploy  

Firebase will provide a URL (e.g., https://realtime-chat-app.web.app) where your app is live!

Enhancements and Next Steps

Now that you have a basic chat app, consider adding these features:

  • User Avatars: Let users upload profile pictures (use Firebase Storage).
  • Online Status: Show when users are online/offline (track with Firestore presence).
  • Read Receipts: Mark messages as “read” when seen by the recipient.
  • File Attachments: Allow sending images/files (Firebase Storage + Firestore).
  • Typing Indicators: Show when a user is typing.

Conclusion

In this tutorial, you built a real-time chat app using Vue.js and Firebase. You learned how to:

  • Set up Vue.js with Vuex and Vue Router.
  • Integrate Firebase Authentication for user registration/login.
  • Use Cloud Firestore for real-time data storage and synchronization.
  • Deploy the app with Firebase Hosting.

Firebase’s real-time capabilities and Vue’s reactivity make this stack ideal for building interactive apps with minimal backend code.

References

For the complete code, check out the GitHub Repository (replace with your repo link).