Why Real-Time Matters?
Real-time communication enhances user experience (UX), and real-time updates on what has happened. You would not want to reload to check if you have received a message right? Or check if you have received any notifications. Following are just a few basic examples of real-time communication:
- Chat Applications: Message b/w users, chatbots, etc.
- Live notifications: Alerts, Status Updates, etc.
Some complex applications for real-time:
- Online Multiplayer Games i.e. Xiangqi chess.
- Open-telemetry tools i.e. Prometheus, new relics where you get real-time updates on how your application is doing in terms of response time and traffic like RPM (requests per minute).
- Collaboration Tools: Simultaneous edits in a shared document.
- IoT Device Dashboards.
- Real-Time Dashboards and Analytics.
Web socket is a persistent, full-duplex communication channel between the client and server. Unlike HTTP, which is request-driven, WebSockets maintain a continuous connection, enabling real-time data exchange.
Choices in WebSocket Libraries
native WebSockets offer direct control, vs libraries like Socket.IO, SockJs offers the following:
- Automatic reconnection.
- Room-based communication for grouping clients.
- Compatibility fallbacks (e.g., using polling when WebSockets are unavailable).
Firebase for Real-Time Applications
Firebase, Google’s Backend-as-a-Service (BaaS) platform, offers a Realtime Database and Cloud Firestore, both of which support live synchronization of data across devices.
Why Firebase?
- Quick integration with mobile and web SDKs.
- A million connections can be created simultaneously without any issues.
- Ensures data availability even when offline.
Resource
Implement a Collaborative Document Editor
Real-time collaboration tools enable multiple users to work on the same document simultaneously. Let's move step-by-step to implement a collaborative document editor using Firebase for data persistence and WebSockets for real-time communication.
Why do we need to Combine Firebase and WebSockets?
- Firebase offers robust data persistence and synchronization across devices, with offline support and security rules.
- WebSockets provide the possibility of low-latency, event-driven communication for real-time collaboration.
- Combined together - Firebase guarantees stable and synchronous storage, and WebSockets offers instant updates to connected clients.
Features of the Collaborative Editor
- Real-time text synchronization across users.
- Change tracking with user attribution.
- Conflict resolution and offline support.
Step-by-Step Implementation
1. Setting Up the Project
Create a New Project Directory
mkdir collaborative-editor
cd collaborative-editor
Initialize a Node.js Project
npm init -y
Install Firebase SDK Inside the project directory:
npm install firebase
npm install -g http-server
Set Up the Directory Structure
mkdir client
2. Setting Up Firebase
Create a Firebase Project
- Go to the Firebase Console.
- Create a new project and enable Realtime Database or Cloud Firestore.
Initialize Firebase in Your Project
// client/firebaseConfig.js
import { initializeApp } from "https://www.gstatic.com/firebasejs/9.22.2/firebase-app.js";
import { getDatabase } from "https://www.gstatic.com/firebasejs/9.22.2/firebase-database.js";
const firebaseConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "YOUR_PROJECT_ID.firebaseapp.com",
databaseURL: "https://YOUR_PROJECT_ID.firebaseio.com",
projectId: "YOUR_PROJECT_ID",
storageBucket: "YOUR_PROJECT_ID.appspot.com",
messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
appId: "YOUR_APP_ID"
};
const app = initializeApp(firebaseConfig);
export const db = getDatabase(app);
3. Building the Collaborative Editor Frontend
Create an HTML File Inside the client directory:
<!-- client/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Collaborative Editor</title>
</head>
<body>
<textarea id="editor" style="width: 100%; height: 80vh;"></textarea>
<script src="./index.js" type="module"></script>
</body>
</html>
Implement Real-Time Synchronization:
// client/index.js
import { db } from "./firebaseConfig.js";
import {
ref,
onValue,
set,
} from "https://www.gstatic.com/firebasejs/9.22.2/firebase-database.js";
const docRef = ref(db, "documents/doc1");
const textArea = document.getElementById("editor");
// Listen for Firebase updates
onValue(docRef, (snapshot) => {
const data = snapshot.val();
if (data && textArea.value !== data.text) {
textArea.value = data.text;
}
});
// Save text to Firebase
textArea.addEventListener('input', () => {
const text = textArea.value;
set(docRef, { text });
});
To run the implementation
cd client
http-server
You should be able to do the following at this point.

Let's enhance the UI a little bit and add interesting features.
Enhancing the UI
1. Update HTML file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Collaborative Editor</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f4f4f9;
}
header {
background-color: #6200ea;
color: white;
padding: 1rem;
text-align: center;
}
textarea {
width: 100%;
height: calc(80vh - 2rem);
border: none;
resize: none;
padding: 1rem;
font-size: 1rem;
background: white;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
outline: none;
margin: 1rem auto;
}
footer {
text-align: center;
padding: 1rem;
font-size: 0.9rem;
background-color: #6200ea;
color: white;
}
button {
margin: 0.5rem;
padding: 0.5rem 1rem;
background: #6200ea;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
button:hover {
background: #3700b3;
}
</style>
</head>
<body>
<header>
<h1>Collaborative Editor</h1>
</header>
<textarea id="editor" placeholder="Start typing..."></textarea>
<footer>
<button id="clearButton">Clear</button>
<button id="exportButton">Export</button>
</footer>
<script src="./index.js" type="module"></script>
</body>
</html>
2. Update index.js file
Clear Text and Export Text
// Clear text for all users
document.getElementById("clearButton").addEventListener("click", () => {
if (confirm("Are you sure you want to clear the document?")) {
set(docRef, { text: "" });
}
});
// Export text to a .txt file
document.getElementById("exportButton").addEventListener("click", () => {
const text = textArea.value;
const blob = new Blob([text], { type: "text/plain" });
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = "document.txt";
link.click();
});
Add User Presence
Update firebaseConfig.js (new presence logic):
import { initializeApp } from "https://www.gstatic.com/firebasejs/9.22.2/firebase-app.js";
import { getDatabase, ref, onDisconnect, set } from "https://www.gstatic.com/firebasejs/9.22.2/firebase-database.js";
const firebaseConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "YOUR_PROJECT_ID.firebaseapp.com",
databaseURL: "https://YOUR_PROJECT_ID.firebaseio.com",
projectId: "YOUR_PROJECT_ID",
storageBucket: "YOUR_PROJECT_ID.appspot.com",
messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
appId: "YOUR_APP_ID",
};
const app = initializeApp(firebaseConfig);
const db = getDatabase(app);
// User presence tracking
const userRef = ref(db, "users/" + Math.random().toString(36).substring(2));
set(userRef, true);
onDisconnect(userRef).remove();
export { db };
Update index.js:
// Updated imports
import { db } from "./firebaseConfig.js";
import {
ref,
onValue,
set,
onDisconnect,
get,
child,
} from "https://www.gstatic.com/firebasejs/9.22.2/firebase-database.js";
// User presence logic
const usersRef = ref(db, "users");
onValue(usersRef, (snapshot) => {
const users = snapshot.val() ? Object.keys(snapshot.val()).length : 0;
document.querySelector("header").innerHTML = `<h1>Collaborative Editor - ${users} Users Online</h1>`;
});
Add version History
New database structure:
{
"documents": {
"doc1": {
"text": "Current content",
"history": {
"timestamp1": "Previous content",
"timestamp2": "Another previous content"
}
}
}
}
Update index.js:
// Save version history
textArea.addEventListener("input", () => {
const text = textArea.value;
const timestamp = Date.now();
set(docRef, { text });
set(ref(db, `documents/doc1/history/${timestamp}`), text);
});
// Revert to previous version
function revertToVersion(timestamp) {
get(child(docRef, `history/${timestamp}`)).then((snapshot) => {
if (snapshot.exists()) {
set(docRef, { text: snapshot.val() });
}
});
}
Finally, here is how your implementation should look and work:

3. Handling Conflicts and Offline Support
Conflict Resolution
- Firebase automatically handles most synchronization issues. To further refine:
- Use timestamps or user IDs to track edits.
- Implement a merge strategy to reconcile conflicting updates.
Offline Mode
- Firebase automatically queues offline changes and syncs them when the connection is restored.
- Test offline functionality to ensure a seamless user experience.
Best Practices
Security
- Use Firebase security rules to restrict read/write access.
- An example rule to allow access only to authenticated users.
{
"rules": {
"documents": {
"$docId": {
".read": "auth != null",
".write": "auth != null"
}
}
}
}
Performance Optimization
- Minimize the size of data synced in real-time.
- Use database indexing for efficient querying.
Testing
- Simulate multi-user scenarios to test real-time updates.
- Validate offline support thoroughly.
In The End
Adding real-time features with WebSockets and Firebase makes web apps more interactive. Following best practices ensures they are secure and efficient. As users expect instant updates, using these tools can help create smooth and engaging experiences. Real-time tech is becoming a must-have for modern web apps. Investing in it now will keep your app fast, responsive, and ready for the future.