
Install firebase and vuefire, initialise the app with initializeApp and getFirestore, then read a collection with useCollection: the list updates itself. Develop against the local Firestore emulator rather than against the production project.
Firebase gives you a database, authentication and hosting with no server code to write. Plugged into Vue, it becomes a real-time backend whose changes propagate into the interface on their own. Here is how to wire it up cleanly with the modular SDK.
Firestore or Realtime Database?
Firebase offers two databases. Realtime Database is the older one: a single large JSON tree, with limited queries. Cloud Firestore organises data into collections of documents, and supports proper compound queries and indexes. For a new project, take Firestore, that is what this article does.
Installing
npm install firebase vuefireVueFire is the official layer connecting Firebase to Vue reactivity. It is not compulsory, but it saves you writing subscriptions and their cleanup by hand.
Initialising
Create the project in the Firebase console, add a web app, and copy the configuration. Keep it in a file of its own:
import { initializeApp } from 'firebase/app'
import { getFirestore } from 'firebase/firestore'
export const firebaseApp = initializeApp({
apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
authDomain: 'mon-projet.firebaseapp.com',
projectId: 'mon-projet'
})
export const db = getFirestore(firebaseApp)Despite what the name suggests, it is not a secret: it ships in the JavaScript bundle and anyone can read it. It identifies the project, it does not authorise anything. Security rests entirely on the Firestore rules and on authentication. Writing those rules is the real security work, not hiding this key.
Then plug VueFire into the application:
import { createApp } from 'vue'
import { VueFire } from 'vuefire'
import App from './App.vue'
import { firebaseApp } from './firebase'
createApp(App)
.use(VueFire, { firebaseApp })
.mount('#app')Reading a collection
useCollection returns a reactive reference, filled and then kept up to date by Firestore:
<script setup>
import { collection } from 'firebase/firestore'
import { useCollection } from 'vuefire'
import { db } from '@/firebase'
const avis = useCollection(collection(db, 'avis'))
</script>
<template>
<ul>
<li v-for="a in avis" :key="a.id">{{ a.nom }} : {{ a.note }}/5</li>
</ul>
</template>That is all it takes: the v-for loop renders the collection like any other array. Open the Firebase console and edit a document: the list changes in the browser with no reload. VueFire closes the subscription when the component unmounts, which avoids the classic leak of forgotten listeners.
For a single document, useDocument follows the same logic:
import { doc } from 'firebase/firestore'
import { useDocument } from 'vuefire'
const produit = useDocument(doc(db, 'produits', '2234'))Filtering and sorting
Queries are composed with query, where, orderBy and limit:
import { collection, limit, orderBy, query, where } from 'firebase/firestore'
const meilleursAvis = useCollection(
query(
collection(db, 'avis'),
where('note', '>=', 4),
orderBy('note', 'desc'),
limit(10)
)
)A query combining a filter and a sort on different fields needs a composite index. Firestore rejects the query and prints a link in the console that creates the index in one click, it is the most useful error message in the product.
Writing
Writes go through the SDK functions, not through VueFire. Wire them to a form to create documents from the interface:
import {
addDoc, collection, deleteDoc, doc, serverTimestamp, setDoc, updateDoc
} from 'firebase/firestore'
import { db } from '@/firebase'
// id generated by Firestore
await addDoc(collection(db, 'avis'), {
nom: 'Damien',
note: 5,
creeLe: serverTimestamp()
})
// chosen id (creates or replaces)
await setDoc(doc(db, 'avis', 'avis-2234'), { nom: 'Damien', note: 5 })
// change some fields
await updateDoc(doc(db, 'avis', 'avis-2234'), { note: 4 })
// delete
await deleteDoc(doc(db, 'avis', 'avis-2234'))serverTimestamp() lets Firestore set the timestamp server-side: the clock on the visitor’s machine is not to be trusted.
Developing against the emulator
Working straight on the production project damages your data and eats into the quota. The Firebase emulator suite runs Firestore locally.
npm install -D firebase-tools
npx firebase init emulators # tick Firestore
npx firebase emulators:start --only firestore --project demo-boutiqueA project id starting with demo- tells the emulator to run with no account and no billing.
On the application side, connect the SDK to the emulator in development only:
import { connectFirestoreEmulator, getFirestore } from 'firebase/firestore'
export const db = getFirestore(firebaseApp)
if (import.meta.env.DEV) {
connectFirestoreEmulator(db, '127.0.0.1', 8080)
}Since firebase-tools 15, an earlier JDK makes startup fail with the message “firebase-tools no longer supports Java version before 21”. Check with java -version.
Security rules
With no rules, the database is either closed or open to everyone. The “test” mode offered when you create it expires after thirty days, do not leave it in place in production.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /avis/{avisId} {
// public read access
allow read: if true;
// writing reserved for signed-in users, on their own review
allow create: if request.auth != null
&& request.resource.data.auteurId == request.auth.uid;
allow update, delete: if request.auth != null
&& resource.data.auteurId == request.auth.uid;
}
}
}Rules are written and tested against the emulator too, which saves you discovering a hole in production.
What changed since SDK 8
Plenty of examples still online use the old namespaced API. It has not worked as written since Firebase 9:
// old SDK (8 and earlier) — no longer works
import firebase from 'firebase'
const db = firebase.initializeApp(config).database()
db.ref('avis').push({ nom: 'Damien' })
// modular SDK (9 and later)
import { initializeApp } from 'firebase/app'
import { addDoc, collection, getFirestore } from 'firebase/firestore'
const db = getFirestore(initializeApp(config))
await addDoc(collection(db, 'avis'), { nom: 'Damien' })Splitting the API into imported functions is not cosmetic: it lets the bundler keep only what you actually use. On an application that only reads from Firestore, the difference in weight is obvious.
On the VueFire side, the old firebase: { … } component option and the $firebaseRefs object are gone: everything goes through useCollection and useDocument.
Going further
This setup assumes you are comfortable with Vue components and reactivity. If not, start with the Vue 3 tutorial for beginners. To share Firebase data between distant components, a Pinia store wrapping the Firestore calls is a good pattern.
Common errors
firebase.initializeApp(config).database() and the global firebase object have not worked since version 9. Everything goes through imported functions.firebase: { … } component option and $firebaseRefs are gone: use useCollection and useDocument.

