Building a Reusable Firebase Service Class with CRUD and Authentication in Vanilla JavaScript
Managing CRUD operations and authentication can be daunting when building an application with Firebase, especially in a vanilla JavaScript project. This article walks you through creating a FirebaseService
class that centralizes Firebase CRUD operations, simplifies authentication, and provides intuitive feedback using toast notifications—all in a clean, reusable, and vanilla JavaScript-friendly way.
Prerequisites
- Firebase Account: Create a Firebase account and set up a new project.
- Basic Knowledge of JavaScript: Familiarity with classes, modules, and importing libraries.
- Toastify Library: For visual alerts, you’ll use Toastify, a lightweight toast notification library. No installation needed as we’ll use a CDN link.
Let’s dive in!
Step 1: Setting Up Firebase and Toastify CDN Links
Since this project uses vanilla JavaScript, we’ll rely on CDN imports for Firebase and Toastify.
In your index.html
file, include the following scripts:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Firebase CRUD with Vanilla JavaScript</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css">
</head>
<body>
<script type="module" src="./main.js"></script>
</body>
</html>
Step 2: Configuring Firebase and Toastify in a Reusable Service Class
The FirebaseService
class manages Firebase’s database and authentication features. The goal here is to make CRUD functions easy to use and reusable in any part of the application.
Create a file FirebaseService.js
:
// FirebaseService.js
import { initializeApp } from 'https://www.gstatic.com/firebasejs/9.18.0/firebase-app.js';
import { getFirestore, collection, addDoc, doc, getDoc, getDocs, updateDoc, deleteDoc } from 'https://www.gstatic.com/firebasejs/9.18.0/firebase-firestore.js';
import { getAuth, createUserWithEmailAndPassword, signInWithEmailAndPassword } from 'https://www.gstatic.com/firebasejs/9.18.0/firebase-auth.js';
import Toastify from 'https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.js';
export default class FirebaseService {
constructor(config) {
this.app = initializeApp(config);
this.db = getFirestore();
this.auth = getAuth();
}
// Display toast notifications
showToast(message, type = "success") {
Toastify({
text: message,
duration: 3000,
close: true,
gravity: "top",
position: "right",
backgroundColor: type === "success" ? "green" : "red",
}).showToast();
}
async createDocument(collectionName, data) {
try {
const docRef = await addDoc(collection(this.db, collectionName), data);
this.showToast("Document created successfully!");
return docRef.id;
} catch (error) {
this.showToast(`Error creating document: ${error.message}`, "error");
throw error;
}
}
async readDocument(collectionName, documentId) {
try {
const docRef = doc(this.db, collectionName, documentId);
const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
this.showToast("Document retrieved successfully!");
return { id: docSnap.id, ...docSnap.data() };
} else {
this.showToast("No such document found", "error");
return null;
}
} catch (error) {
this.showToast(`Error reading document: ${error.message}`, "error");
throw error;
}
}
async readAllDocuments(collectionName) {
try {
const snapshot = await getDocs(collection(this.db, collectionName));
this.showToast("All documents retrieved successfully!");
return snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
} catch (error) {
this.showToast(`Error reading documents: ${error.message}`, "error");
throw error;
}
}
async updateDocument(collectionName, documentId, data) {
try {
const docRef = doc(this.db, collectionName, documentId);
await updateDoc(docRef, data);
this.showToast("Document updated successfully!");
} catch (error) {
this.showToast(`Error updating document: ${error.message}`, "error");
throw error;
}
}
async deleteDocument(collectionName, documentId) {
try {
const docRef = doc(this.db, collectionName, documentId);
await deleteDoc(docRef);
this.showToast("Document deleted successfully!");
} catch (error) {
this.showToast(`Error deleting document: ${error.message}`, "error");
throw error;
}
}
async registerUser(credentials, userData) {
try {
const { email, password } = credentials;
const userCredential = await createUserWithEmailAndPassword(this.auth, email, password);
const userId = userCredential.user.uid;
await this.createDocument("users", { uid: userId, ...userData });
this.showToast("User registered successfully!");
return userId;
} catch (error) {
this.showToast(`Error registering user: ${error.message}`, "error");
throw error;
}
}
async loginUser(email, password) {
try {
const userCredential = await signInWithEmailAndPassword(this.auth, email, password);
this.showToast("Login successful!");
return userCredential.user.uid;
} catch (error) {
this.showToast(`Error logging in: ${error.message}`, "error");
throw error;
}
}
}
Step 3: Using FirebaseService
in Your Application
The FirebaseService
class simplifies working with Firebase and lets you manage CRUD operations for any part of the application. Let’s see how you can register and authenticate a user.
- Initialize the FirebaseService with your Firebase config:
// main.js
import FirebaseService from './FirebaseService.js';
const firebaseConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "YOUR_AUTH_DOMAIN",
projectId: "YOUR_PROJECT_ID",
storageBucket: "YOUR_STORAGE_BUCKET",
messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
appId: "YOUR_APP_ID",
};
const firebaseService = new FirebaseService(firebaseConfig);
2. Register a New User:
async function registerNewUser() {
const credentials = {
email: "user@example.com",
password: "securePassword123",
};
const userData = {
firstName: "John",
lastName: "Doe",
age: 30,
};
try {
await firebaseService.registerUser(credentials, userData);
} catch (error) {
console.error("Registration error:", error.message);
}
}
registerNewUser();
3. User Login:
async function loginUser() {
const email = "user@example.com";
const password = "securePassword123";
try {
await firebaseService.loginUser(email, password);
} catch (error) {
console.error("Login error:", error.message);
}
}
loginUser();
Step 4: Adding Toast Notifications
Using Toastify in your FirebaseService
class ensures users receive feedback about their actions without requiring manual console logging.
The showToast
method displays notifications for successful or failed CRUD actions, improving the user experience and keeping the UI responsive.
Use Case: CRUD Operations for a Chat Application with FirebaseService
In this section, we will explore how to implement CRUD (Create, Read, Update, Delete) operations in a chat application using the FirebaseService
class. This application allows users to register, send messages, retrieve chat history, update their profiles, and delete messages.
1. Create: Adding New Users and New Messages
User Registration
When a new user wants to join the chat application, they must register by providing their email, password, and personal details (like first and last names). The registration function uses the registerUser
method in the FirebaseService
class.
Example:
const credentials = { email: "john@example.com", password: "password123" };
const userData = { firstName: "John", lastName: "Doe", email: "john@example.com" };
firebaseService.registerUser(credentials, userData)
.then(userId => console.log("User registered with ID:", userId))
.catch(error => console.error(error));
Sending a Message
Users can send messages to each other. When a user sends a message, a new document representing the chat message is created in the “chats” collection.
Example:
const messageData = {
senderId: "userId1",
receiverId: "userId2",
message: "Hello! How are you?",
timestamp: new Date()
};
firebaseService.createDocument("chats", messageData)
.then(docId => console.log("Message sent with ID:", docId))
.catch(error => console.error(error));
2. Read: Retrieving a List of Users and Reading Chat Messages
Retrieving Users
To display the list of registered users in the chat application, we can retrieve all documents from the “users” collection using the readAllDocuments
method.
Example:
firebaseService.readAllDocuments("users")
.then(users => console.log("Registered users:", users))
.catch(error => console.error(error));
Reading Chat Messages
To read chat messages, we can retrieve all messages from the “chats” collection. This allows users to see their chat history.
Example:
firebaseService.readAllDocuments("chats")
.then(messages => console.log("Chat messages:", messages))
.catch(error => console.error(error));
3. Update: Updating User Information or Modifying a Chat Message
Updating User Information
Users can update their profile information, such as their first name, last name, or email. We use the updateDocument
method to modify the user's document in the "users" collection.
Example:
const updatedUserData = { firstName: "Johnathan", lastName: "Doe" };
firebaseService.updateDocument("users", "userId1", updatedUserData)
.then(() => console.log("User information updated successfully"))
.catch(error => console.error(error));
Modifying a Chat Message
If a user wants to edit a message they sent, we can update that message in the “chats” collection using the updateDocument
method.
Example:
const updatedMessageData = { message: "Hi! How have you been?" };
firebaseService.updateDocument("chats", "messageDocId", updatedMessageData)
.then(() => console.log("Message updated successfully"))
.catch(error => console.error(error));
4. Delete: Removing Users or Deleting a Specific Chat Message
Removing Users
To remove a user from the chat application, we can delete their document from the “users” collection.
Example:
firebaseService.deleteDocument("users", "userId1")
.then(() => console.log("User deleted successfully"))
.catch(error => console.error(error));
Deleting a Chat Message
If a user wants to delete a message they sent, we can remove that message from the “chats” collection using the deleteDocument
method.
Example:
firebaseService.deleteDocument("chats", "messageDocId")
.then(() => console.log("Message deleted successfully"))
.catch(error => console.error(error));
Conclusion
The FirebaseService
class provides a clean and reusable structure for performing CRUD operations in a chat application. It simplifies user management and message handling by encapsulating Firebase functionality within a single class, allowing developers to focus on building features without delving deep into Firebase's API each time.
This approach allows you to use Firebase seamlessly in a vanilla JavaScript project with centralized CRUD operations and authentication. Using Toastify for feedback keeps interactions user-friendly and accessible, making FirebaseService
a flexible, reusable solution for Firebase-based applications. By following this structure, developers can create robust chat applications that efficiently manage user data and messages, enhancing the overall user experience.