Building a Reusable Firebase Service Class with CRUD and Authentication in Vanilla JavaScript

Smith Kruz
5 min readOct 31, 2024

--

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

  1. Firebase Account: Create a Firebase account and set up a new project.
  2. Basic Knowledge of JavaScript: Familiarity with classes, modules, and importing libraries.
  3. 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.

  1. 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.

--

--

Smith Kruz
Smith Kruz

Written by Smith Kruz

Experienced backend developer proficient in PHP, Node.js, Express, and MySQL. Dedicated to crafting efficient and secure web solutions. Let's innovate together!

No responses yet