Firebase Integration: Auth, Firestore, and Realtime Database

Firebase Integration: Auth, Firestore, and Realtime Database

Firebase has revolutionized modern web and mobile app development by providing a comprehensive Backend-as-a-Service (BaaS) platform. This guide explores three core Firebase services: Authentication, Firestore, and Realtime Database, showing you how to integrate them effectively into your applications.

What is Firebase?

Firebase is Google’s mobile and web application development platform that provides developers with tools and infrastructure to build high-quality apps quickly. It offers real-time data synchronization, authentication services, hosting, analytics, and much more, all managed in the cloud.

Firebase Authentication: Secure User Management Made Simple

Firebase Authentication provides backend services and SDKs to authenticate users in your app. It supports various authentication methods and integrates seamlessly with other Firebase services.

Key Features of Firebase Auth

  • Multiple Authentication Methods: Email/password, phone number, Google, Facebook, Twitter, GitHub, and more
  • Security: Built-in security features including email verification and password reset
  • User Management: Easy user profile management and custom claims
  • Multi-platform Support: Works across web, iOS, and Android

Setting Up Firebase Authentication

First, install the Firebase SDK:

npm install firebase

Initialize Firebase in your project:

import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';

const firebaseConfig = {
  // Your config object from Firebase Console
};

const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);

Implementing Email/Password Authentication

import { 
  createUserWithEmailAndPassword, 
  signInWithEmailAndPassword,
  signOut,
  onAuthStateChanged 
} from 'firebase/auth';

// Register a new user
const registerUser = async (email, password) => {
  try {
    const userCredential = await createUserWithEmailAndPassword(auth, email, password);
    console.log('User registered:', userCredential.user);
  } catch (error) {
    console.error('Registration error:', error.message);
  }
};

// Sign in existing user
const signInUser = async (email, password) => {
  try {
    const userCredential = await signInWithEmailAndPassword(auth, email, password);
    console.log('User signed in:', userCredential.user);
  } catch (error) {
    console.error('Sign-in error:', error.message);
  }
};

// Sign out user
const signOutUser = async () => {
  try {
    await signOut(auth);
    console.log('User signed out');
  } catch (error) {
    console.error('Sign-out error:', error.message);
  }
};

// Listen for authentication state changes
onAuthStateChanged(auth, (user) => {
  if (user) {
    console.log('User is signed in:', user.uid);
  } else {
    console.log('User is signed out');
  }
});

Cloud Firestore: Modern NoSQL Database

Firebase Integration: Auth, Firestore, and Realtime Database

Cloud Firestore is Firebase’s flagship database solution, offering real-time synchronization, offline support, and powerful querying capabilities.

Firestore Key Features

  • Real-time Updates: Automatically syncs data across all connected clients
  • Offline Support: Works offline and syncs when connectivity returns
  • Scalable: Automatically scales to handle your app’s growth
  • ACID Transactions: Supports atomic transactions
  • Security Rules: Declarative security rules for data protection

Setting Up Firestore

import { getFirestore, doc, setDoc, getDoc, collection, addDoc } from 'firebase/firestore';

const db = getFirestore(app);

Basic Firestore Operations

Adding Data

// Add a document with auto-generated ID
const addUser = async (userData) => {
  try {
    const docRef = await addDoc(collection(db, "users"), userData);
    console.log("Document written with ID: ", docRef.id);
  } catch (error) {
    console.error("Error adding document: ", error);
  }
};

// Set a document with custom ID
const setUserProfile = async (userId, profileData) => {
  try {
    await setDoc(doc(db, "userProfiles", userId), profileData);
    console.log("Profile updated successfully");
  } catch (error) {
    console.error("Error updating profile: ", error);
  }
};

Reading Data

import { getDocs, query, where, orderBy, limit } from 'firebase/firestore';

// Get a single document
const getUserProfile = async (userId) => {
  try {
    const docRef = doc(db, "userProfiles", userId);
    const docSnap = await getDoc(docRef);
    
    if (docSnap.exists()) {
      return docSnap.data();
    } else {
      console.log("No such document!");
    }
  } catch (error) {
    console.error("Error getting document:", error);
  }
};

// Query multiple documents
const getActiveUsers = async () => {
  try {
    const q = query(
      collection(db, "users"),
      where("status", "==", "active"),
      orderBy("lastSeen", "desc"),
      limit(10)
    );
    
    const querySnapshot = await getDocs(q);
    const users = [];
    querySnapshot.forEach((doc) => {
      users.push({ id: doc.id, ...doc.data() });
    });
    
    return users;
  } catch (error) {
    console.error("Error getting users:", error);
  }
};

Real-time Listeners

import { onSnapshot } from 'firebase/firestore';

// Listen to real-time updates
const listenToUserUpdates = (userId, callback) => {
  const userRef = doc(db, "userProfiles", userId);
  
  const unsubscribe = onSnapshot(userRef, (doc) => {
    if (doc.exists()) {
      callback({ id: doc.id, ...doc.data() });
    }
  });
  
  // Return unsubscribe function to stop listening
  return unsubscribe;
};

Realtime Database: Real-time Data Synchronization

Firebase Realtime Database is a cloud-hosted NoSQL database that synchronizes data in real-time across all connected clients.

Realtime Database Features

  • Real-time Synchronization: Data changes instantly across all clients
  • Offline Capabilities: Works offline with automatic synchronization
  • JSON Tree Structure: Data stored as one large JSON tree
  • REST API: Access data through REST endpoints

Setting Up Realtime Database

import { getDatabase, ref, set, get, push, onValue } from 'firebase/database';

const database = getDatabase(app);

Basic Realtime Database Operations

Writing Data

// Write data to a reference
const writeUserData = (userId, name, email) => {
  set(ref(database, 'users/' + userId), {
    username: name,
    email: email,
    timestamp: Date.now()
  }).then(() => {
    console.log("Data saved successfully!");
  }).catch((error) => {
    console.error("Error saving data:", error);
  });
};

// Push data (auto-generated key)
const addMessage = (chatId, message) => {
  const messagesRef = ref(database, `chats/${chatId}/messages`);
  push(messagesRef, {
    text: message,
    timestamp: Date.now(),
    sender: auth.currentUser.uid
  });
};

Reading Data

// Read data once
const getUserData = async (userId) => {
  try {
    const snapshot = await get(ref(database, 'users/' + userId));
    if (snapshot.exists()) {
      return snapshot.val();
    } else {
      console.log("No data available");
    }
  } catch (error) {
    console.error("Error reading data:", error);
  }
};

// Listen for real-time updates
const listenToMessages = (chatId, callback) => {
  const messagesRef = ref(database, `chats/${chatId}/messages`);
  
  onValue(messagesRef, (snapshot) => {
    const messages = [];
    snapshot.forEach((childSnapshot) => {
      messages.push({
        id: childSnapshot.key,
        ...childSnapshot.val()
      });
    });
    callback(messages);
  });
};

Firestore vs Realtime Database: When to Use Which?

Choose Firestore When:

  • You need complex queries and indexing
  • Your app requires offline support with complex data structures
  • You want better scalability and multi-region support
  • You need structured data with collections and documents
  • You require ACID transactions

Choose Realtime Database When:

  • You need simple real-time features (like chat apps)
  • You have simple data structures
  • You want lower latency for real-time updates
  • Your queries are simple and you don’t need complex filtering

Integrating Authentication with Database Services

Firebase Integration: Auth, Firestore, and Realtime Database

Securing Firestore with Auth

// Firestore security rules example
/*
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // Users can only access their own documents
    match /userProfiles/{userId} {
      allow read, write: if request.auth != null && request.auth.uid == userId;
    }
    
    // Public read, authenticated write
    match /posts/{postId} {
      allow read: if true;
      allow write: if request.auth != null;
    }
  }
}
*/

// Create user profile after authentication
const createUserProfile = async (user) => {
  const userProfile = {
    uid: user.uid,
    email: user.email,
    createdAt: new Date(),
    displayName: user.displayName || '',
    photoURL: user.photoURL || ''
  };
  
  await setDoc(doc(db, "userProfiles", user.uid), userProfile);
};

Securing Realtime Database with Auth

// Realtime Database security rules example
/*
{
  "rules": {
    "users": {
      "$uid": {
        ".read": "$uid === auth.uid",
        ".write": "$uid === auth.uid"
      }
    },
    "publicPosts": {
      ".read": true,
      ".write": "auth != null"
    }
  }
}
*/

Best Practices for Firebase Integration

Authentication Best Practices

  1. Always validate user input before authentication attempts
  2. Implement proper error handling for different authentication scenarios
  3. Use email verification for enhanced security
  4. Implement password reset functionality for better user experience
  5. Store additional user data in Firestore/Realtime Database, not in Auth

Database Best Practices

  1. Structure your data efficiently – avoid deep nesting
  2. Use security rules to protect sensitive data
  3. Implement pagination for large datasets
  4. Cache frequently accessed data to reduce read operations
  5. Use transactions for operations that require consistency
  6. Monitor usage to optimize costs

Performance Optimization

  1. Use compound queries efficiently in Firestore
  2. Implement proper indexing for complex queries
  3. Use offline persistence to improve user experience
  4. Batch operations when possible to reduce API calls
  5. Optimize security rules to avoid unnecessary reads

Real-world Example: Chat Application

Here’s how all three services work together in a chat application:

// Complete chat app integration
class ChatApp {
  constructor() {
    this.auth = auth;
    this.db = db;
    this.rtdb = database;
    this.currentUser = null;
    
    // Listen for auth state changes
    onAuthStateChanged(this.auth, (user) => {
      this.currentUser = user;
      if (user) {
        this.initializeUserPresence();
      }
    });
  }
  
  // Initialize user presence in Realtime Database
  initializeUserPresence() {
    const userStatusRef = ref(this.rtdb, `/status/${this.currentUser.uid}`);
    const isOfflineForDatabase = {
      state: 'offline',
      last_changed: Date.now(),
    };
    const isOnlineForDatabase = {
      state: 'online',
      last_changed: Date.now(),
    };
    
    // Set user online when they connect
    set(userStatusRef, isOnlineForDatabase);
    
    // Set user offline when they disconnect
    onDisconnect(userStatusRef).set(isOfflineForDatabase);
  }
  
  // Send message (stored in Firestore)
  async sendMessage(chatId, messageText) {
    const message = {
      text: messageText,
      senderId: this.currentUser.uid,
      senderName: this.currentUser.displayName,
      timestamp: new Date(),
      chatId: chatId
    };
    
    await addDoc(collection(this.db, "messages"), message);
  }
  
  // Listen to messages (Firestore real-time)
  listenToMessages(chatId, callback) {
    const q = query(
      collection(this.db, "messages"),
      where("chatId", "==", chatId),
      orderBy("timestamp", "asc")
    );
    
    return onSnapshot(q, (snapshot) => {
      const messages = snapshot.docs.map(doc => ({
        id: doc.id,
        ...doc.data()
      }));
      callback(messages);
    });
  }
}

Conclusion

Firebase provides a powerful ecosystem for building modern applications with minimal backend complexity. By combining Authentication, Firestore, and Realtime Database, you can create secure, scalable, and real-time applications quickly.

The key to successful Firebase integration is understanding when to use each service and how they complement each other. Authentication provides the security layer, Firestore handles complex data operations, and Realtime Database excels at real-time features.

Start with Firebase Authentication to secure your app, choose between Firestore and Realtime Database based on your data needs, and always implement proper security rules to protect your users’ data. With these foundations in place, you’ll be well-equipped to build robust, modern applications that scale with your user base.

Remember to monitor your Firebase usage, optimize your queries, and follow security best practices to ensure your application remains performant and secure as it grows.

Leave a Reply

Your email address will not be published. Required fields are marked *