Building a high-performance, scalable chat app using Firebase requires optimized database design, efficient queries, and cost-effective strategies. Below are best practices to handle millions of users and messages while keeping performance high and costs low.
| Feature | Firestore | Realtime Database |
|---|---|---|
| Scalability | Horizontally scales | Limited scaling |
| Query Performance | Indexed queries | Nested queries slow down |
| Cost Efficiency | Pay-per-use (reads/writes) | Charges for data size & connections |
| Offline Support | Strong caching | Basic caching |
To handle high throughput, structure data for fast reads & minimal writes.
/chats/{chatId}/messages/{messageId} <-- Subcollection for scalability
/chats/{chatId}/members/{userId} <-- Tracks members in a chat
/users/{userId} <-- Stores user profile & last seen
/chats/chat123
{
"chatName": "Developers Group",
"lastMessage": "Hello!",
"lastMessageTime": 1700000000,
"members": ["userA", "userB"]
}
/chats/chat123/messages/messageXYZ
{
"senderId": "userA",
"text": "Hello!",
"timestamp": 1700000000
}
/users/userA
{
"name": "John Doe",
"profilePic": "url",
"lastSeen": 1700000000
}
/messages/{messageId} instead of arrays)?Large-scale chats require pagination & indexing.
const messagesRef = db.collection("chats/chat123/messages")
.orderBy("timestamp", "desc")
.limit(20);
const snapshot = await messagesRef.get();
const messages = snapshot.docs.map(doc => doc.data());
startAfter() for pagination :const lastDoc = snapshot.docs[snapshot.docs.length - 1];
const nextPage = await db.collection("chats/chat123/messages")
.orderBy("timestamp", "desc")
.startAfter(lastDoc)
.limit(20)
.get();
Use onSnapshot() to get real-time updates without excessive reads.
db.collection("chats/chat123/messages")
.orderBy("timestamp", "desc")
.limit(10)
.onSnapshot(snapshot => {
snapshot.docChanges().forEach(change => {
if (change.type === "added") {
console.log("New message:", change.doc.data());
}
});
});
* Reduces Firestore reads by only fetching new messages instead of reloading everything.
Since Firestore doesn’t support real-time presence, use Realtime Database.
/status/userA
{
"online": true,
"lastSeen": 1700000000
}
const userStatusRef = firebase.database().ref("/status/userA");
firebase.auth().onAuthStateChanged(user => {
if (user) {
userStatusRef.set({ online: true });
window.addEventListener("beforeunload", () => {
userStatusRef.set({ online: false, lastSeen: Date.now() });
});
}
});
* Keeps presence updates fast while storing user messages in Firestore.
Use Firebase Cloud Messaging (FCM) to notify users of new messages.
const admin = require("firebase-admin");
exports.sendMessageNotification = functions.firestore
.document("chats/{chatId}/messages/{messageId}")
.onCreate(async (snap, context) => {
const messageData = snap.data();
const payload = {
notification: {
title: "New Message!",
body: messageData.text,
click_action: "FLUTTER_NOTIFICATION_CLICK"
},
token: "USER_FCM_TOKEN"
};
await admin.messaging().send(payload);
});
* Push notifications keep users engaged without excessive polling.
.select().onSnapshot() instead of polling.lastMessage for every message).