Skip to main content
Vue.js for Beginners to Advanced
CHAPTER 23 Beginner

Vue with Firebase

Updated: May 18, 2026
5 min read

# CHAPTER 23

Vue with Firebase

1. Chapter Introduction

Firebase provides a complete backend-as-a-service — authentication, real-time database, file storage, and hosting — all without writing server code. Integrating Firebase with Vue gives you a production-ready full-stack app in hours.

2. Learning Objectives

  • Set up Firebase in a Vue project.
  • Implement Firebase Authentication (email/Google).
  • Use Firestore for real-time data.
  • Listen to real-time updates.
  • Build a Firebase-powered todo app.

3. Setup

bash
1
npm install firebase
javascript
1234567891011121314151617
// src/firebase/config.js
import { initializeApp } from 'firebase/app'
import { getAuth } from 'firebase/auth'
import { getFirestore } from 'firebase/firestore'

const firebaseConfig = {
  apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
  authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
  projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
  storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
  appId: import.meta.env.VITE_FIREBASE_APP_ID
}

const app = initializeApp(firebaseConfig)
export const auth = getAuth(app)
export const db = getFirestore(app)

4. Firebase Authentication

javascript
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
// src/composables/useFirebaseAuth.js
import { ref } from 'vue'
import {
  createUserWithEmailAndPassword,
  signInWithEmailAndPassword,
  signInWithPopup,
  GoogleAuthProvider,
  signOut,
  onAuthStateChanged,
  updateProfile
} from 'firebase/auth'
import { auth } from '@/firebase/config'

export function useFirebaseAuth() {
  const user = ref(null)
  const loading = ref(true)
  const error = ref(null)

  // Listen to auth state changes
  onAuthStateChanged(auth, (firebaseUser) => {
    user.value = firebaseUser
    loading.value = false
  })

  async function register(email, password, displayName) {
    error.value = null
    try {
      const { user: newUser } = await createUserWithEmailAndPassword(auth, email, password)
      await updateProfile(newUser, { displayName })
      user.value = newUser
    } catch (err) {
      error.value = err.message
    }
  }

  async function login(email, password) {
    error.value = null
    try {
      await signInWithEmailAndPassword(auth, email, password)
    } catch (err) {
      error.value = err.message
    }
  }

  async function loginWithGoogle() {
    error.value = null
    try {
      const provider = new GoogleAuthProvider()
      await signInWithPopup(auth, provider)
    } catch (err) {
      error.value = err.message
    }
  }

  async function logout() {
    await signOut(auth)
    user.value = null
  }

  return { user, loading, error, register, login, loginWithGoogle, logout }
}

5. Firestore CRUD

javascript
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
// src/composables/useFirestore.js
import { ref } from 'vue'
import {
  collection, doc, addDoc, getDoc, getDocs,
  updateDoc, deleteDoc, onSnapshot,
  query, where, orderBy, serverTimestamp
} from 'firebase/firestore'
import { db } from '@/firebase/config'

export function useTodos(userId) {
  const todos = ref([])
  const loading = ref(false)
  let unsubscribe = null

  // Real-time listener — updates todos when Firestore changes
  function startListening() {
    const q = query(
      collection(db, 'todos'),
      where('userId', '==', userId),
      orderBy('createdAt', 'desc')
    )
    unsubscribe = onSnapshot(q, (snapshot) => {
      todos.value = snapshot.docs.map(doc => ({
        id: doc.id,
        ...doc.data()
      }))
    })
  }

  async function addTodo(text) {
    await addDoc(collection(db, 'todos'), {
      text,
      done: false,
      userId,
      createdAt: serverTimestamp()
    })
  }

  async function toggleTodo(id, done) {
    await updateDoc(doc(db, 'todos', id), { done: !done })
  }

  async function deleteTodo(id) {
    await deleteDoc(doc(db, 'todos', id))
  }

  function stopListening() {
    if (unsubscribe) unsubscribe()
  }

  return { todos, loading, startListening, stopListening, addTodo, toggleTodo, deleteTodo }
}

6. Mini Project: Firebase Todo App

vue
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
<!-- FirebaseTodo.vue -->
<script setup>
import { ref, onMounted, onUnmounted } from &#039;vue'
import { useFirebaseAuth } from &#039;@/composables/useFirebaseAuth'
import { useTodos } from &#039;@/composables/useFirestore'

const { user, login, logout, loginWithGoogle } = useFirebaseAuth()
const newTodo = ref(&#039;')

// Only start listening when user is logged in
const { todos, startListening, stopListening, addTodo, toggleTodo, deleteTodo } = useTodos(user.value?.uid)

onMounted(() => { if (user.value) startListening() })
onUnmounted(stopListening)

async function handleAdd() {
  if (!newTodo.value.trim()) return
  await addTodo(newTodo.value.trim())
  newTodo.value = &#039;'
}
</script>

<template>
  <div class="firebase-app">
    <div v-if="!user" class="login-screen">
      <h1>🔥 Firebase Todos</h1>
      <button @click="loginWithGoogle" class="google-btn">
        <img src="https://www.google.com/favicon.ico" width="18" />
        Sign in with Google
      </button>
    </div>
    <div v-else class="app">
      <header>
        <h2>Hello, {{ user.displayName }}!</h2>
        <button @click="logout">Logout</button>
      </header>
      <div class="add-row">
        <input v-model="newTodo" @keyup.enter="handleAdd" placeholder="New todo..." />
        <button @click="handleAdd">Add</button>
      </div>
      <ul>
        <li v-for="todo in todos" :key="todo.id">
          <input type="checkbox" :checked="todo.done" @change="toggleTodo(todo.id, todo.done)" />
          <span :class="{ done: todo.done }">{{ todo.text }}</span>
          <button @click="deleteTodo(todo.id)">🗑️</button>
        </li>
        <li v-if="todos.length === 0" class="empty">No todos yet!</li>
      </ul>
    </div>
  </div>
</template>

7. Common Mistakes

  • Forgetting to unsubscribe from onSnapshot: Real-time listeners continue even after component unmounts, causing memory leaks. Always call the unsubscribe function in onUnmounted.
  • Using client config for server operations: Firebase client SDK is for browser. For admin operations (delete any user, etc.), use Firebase Admin SDK on a server.

8. MCQs

Question 1

Firebase onSnapshot provides?

Question 2

serverTimestamp() in Firestore?

Question 3

onAuthStateChanged fires?

Question 4

Firestore query with where filters?

Question 5

Google sign-in uses?

Question 6

deleteDoc(doc(db, 'todos', id)) deletes?

Question 7

Firebase config should be in?

Question 8

Unsubscribe from onSnapshot to?

Question 9

updateDoc updates?

Question 10

Firebase Hosting for Vue requires?

9. Interview Questions

  • Q: How do you implement real-time data updates with Firestore in Vue?
  • Q: Why must you unsubscribe from Firestore listeners?

10. Summary

Firebase + Vue = rapid full-stack development. onAuthStateChanged manages auth state reactively. Firestore's onSnapshot provides real-time updates that sync automatically with Vue's reactivity. Always clean up listeners in onUnmounted to prevent memory leaks.

11. Next Chapter Recommendation

In Chapter 24: Vue with TailwindCSS, we build stunning, responsive UIs by combining Vue's reactivity with Tailwind's utility-first CSS system.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·