Database

Firebase Firestore

Overview

Firebase Firestore is a cloud-hosted NoSQL document database provided by Google. It features real-time synchronization, offline support, and automatic scaling, optimized for mobile, web, and server application development. Designed as the successor to Firebase Realtime Database, it offers more flexible querying capabilities and a serverless architecture.

Details

Firebase Firestore was announced by Google in 2017, developed as an evolution of Firebase Realtime Database. It features the following characteristics:

Key Features

  • Real-time Synchronization: Automatic data synchronization across clients
  • Offline Support: Continues operation without network connectivity
  • Automatic Scaling: Unlimited scaling through serverless architecture
  • Rich Queries: Support for compound indexes, array queries, and geo-location queries
  • ACID Transactions: Atomicity guarantees across multiple documents
  • Multi-region: Data replication and low-latency access worldwide
  • Security Rules: Fine-grained access control and data validation
  • Batch Operations: Efficient execution of multiple read/write operations

2024 New Features

  • Vector Search: Semantic search for AI and machine learning applications
  • MongoDB Compatibility: Use existing MongoDB code and drivers directly
  • Aggregation Queries: Support for SUM, AVERAGE, COUNT aggregation functions
  • LangChain Integration: Support for RAG (Retrieval-Augmented Generation) architectures
  • Performance Improvements: Optimized read and write performance

Data Model

  • Hierarchical Structure: Collections > Documents > Subcollections
  • Schema-less: Flexible JSON-like document structure
  • Nested Arrays & Objects: Support for complex data structures
  • Reference Type: Relationships between documents
  • Geolocation Data: GeoPoint and GeoHash for location processing

Security

  • Firebase Authentication Integration: Integration with user authentication
  • Security Rules: Declarative access control
  • Encryption: Automatic encryption at rest and in transit
  • Audit Logs: Integration with Cloud Logging

Advantages and Disadvantages

Advantages

  • Development Speed: No backend setup required with serverless architecture
  • Real-time Updates: Instant data change reflection across all clients
  • Offline Support: Continued operation during network disconnection
  • Automatic Scaling: Automatic response to traffic increases
  • Rich SDKs: Native SDKs for iOS, Android, Web, and server platforms
  • Firebase Ecosystem: Integration with Authentication, Analytics, and Hosting
  • 99.999% SLA: Industry-leading availability guarantee
  • Free Tier: Sufficient free limits for development and small-scale usage

Disadvantages

  • Vendor Lock-in: Dependency on Firebase/Google Cloud
  • Cost: High pricing for large-scale usage
  • Complex Query Limitations: No support for JOINs or subqueries
  • Data Size Limits: Maximum 1MB per document
  • Relational Constraints: Unsuitable for normalized data structures
  • Cold Start: Initial execution delays typical of serverless
  • Debugging Difficulty: Complex error identification on client-side

Key Links

Code Examples

Installation & Setup

# Install Firebase CLI
npm install -g firebase-tools

# Initialize Firebase project
firebase login
firebase init firestore

# Install Web SDK
npm install firebase

# Install React Native
npm install @react-native-firebase/app
npm install @react-native-firebase/firestore

# Add Flutter plugin
flutter pub add cloud_firestore
// Firebase initialization (Web)
import { initializeApp } from 'firebase/app';
import { getFirestore, connectFirestoreEmulator } from 'firebase/firestore';

const firebaseConfig = {
  apiKey: "your-api-key",
  authDomain: "your-project.firebaseapp.com",
  projectId: "your-project-id",
  storageBucket: "your-project.appspot.com",
  messagingSenderId: "1234567890",
  appId: "your-app-id"
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);

// Connect to emulator (development)
if (process.env.NODE_ENV === 'development') {
  connectFirestoreEmulator(db, 'localhost', 8080);
}

export { db };

Basic Operations (CRUD)

// Basic CRUD operations
import { 
  collection, 
  doc, 
  addDoc, 
  getDoc, 
  getDocs, 
  updateDoc, 
  deleteDoc,
  query,
  where,
  orderBy,
  limit
} from 'firebase/firestore';

// Create document
async function createProduct() {
  try {
    const docRef = await addDoc(collection(db, 'products'), {
      name: 'Wireless Headphones',
      brand: 'Sample Brand',
      price: 150,
      category: 'electronics',
      features: ['Noise Cancelling', 'Bluetooth 5.0', 'Waterproof'],
      specifications: {
        battery: '24 hours',
        weight: '50g',
        colors: ['black', 'white', 'blue']
      },
      availability: {
        inStock: true,
        quantity: 100,
        releaseDate: new Date('2024-01-15')
      },
      reviews: [],
      metadata: {
        createdAt: new Date(),
        updatedAt: new Date(),
        version: 1
      }
    });
    
    console.log('Document created:', docRef.id);
    return docRef.id;
  } catch (error) {
    console.error('Creation error:', error);
  }
}

// Read document
async function getProduct(productId) {
  try {
    const docRef = doc(db, 'products', productId);
    const docSnap = await getDoc(docRef);
    
    if (docSnap.exists()) {
      console.log('Product data:', docSnap.data());
      return docSnap.data();
    } else {
      console.log('Product not found');
      return null;
    }
  } catch (error) {
    console.error('Read error:', error);
  }
}

// Get multiple documents with queries
async function getProducts() {
  try {
    // Basic query
    const q = query(
      collection(db, 'products'),
      where('category', '==', 'electronics'),
      where('price', '<=', 200),
      orderBy('price', 'desc'),
      limit(10)
    );
    
    const querySnapshot = await getDocs(q);
    const products = [];
    
    querySnapshot.forEach((doc) => {
      products.push({
        id: doc.id,
        ...doc.data()
      });
    });
    
    console.log('Product list:', products);
    return products;
  } catch (error) {
    console.error('Query error:', error);
  }
}

// Update document
async function updateProduct(productId, updates) {
  try {
    const docRef = doc(db, 'products', productId);
    await updateDoc(docRef, {
      ...updates,
      'metadata.updatedAt': new Date(),
      'metadata.version': increment(1)
    });
    
    console.log('Product updated');
  } catch (error) {
    console.error('Update error:', error);
  }
}

// Delete document
async function deleteProduct(productId) {
  try {
    await deleteDoc(doc(db, 'products', productId));
    console.log('Product deleted');
  } catch (error) {
    console.error('Delete error:', error);
  }
}

Real-time Synchronization

// Real-time listeners
import { onSnapshot, serverTimestamp } from 'firebase/firestore';

// Watch single document
function watchProduct(productId, callback) {
  const docRef = doc(db, 'products', productId);
  
  const unsubscribe = onSnapshot(docRef, (doc) => {
    if (doc.exists()) {
      callback({
        id: doc.id,
        ...doc.data()
      });
    } else {
      callback(null);
    }
  }, (error) => {
    console.error('Listener error:', error);
  });
  
  return unsubscribe; // For cleanup
}

// Watch collection
function watchProducts(callback) {
  const q = query(
    collection(db, 'products'),
    where('availability.inStock', '==', true),
    orderBy('metadata.updatedAt', 'desc')
  );
  
  const unsubscribe = onSnapshot(q, (querySnapshot) => {
    const products = [];
    
    querySnapshot.docChanges().forEach((change) => {
      const product = {
        id: change.doc.id,
        ...change.doc.data()
      };
      
      if (change.type === 'added') {
        console.log('New product:', product.name);
        products.push(product);
      }
      if (change.type === 'modified') {
        console.log('Product updated:', product.name);
      }
      if (change.type === 'removed') {
        console.log('Product removed:', product.name);
      }
    });
    
    callback(products);
  }, (error) => {
    console.error('Query listener error:', error);
  });
  
  return unsubscribe;
}

// Chat application example
function setupChatListener(roomId, callback) {
  const messagesRef = collection(db, 'chatRooms', roomId, 'messages');
  const q = query(messagesRef, orderBy('timestamp', 'asc'));
  
  return onSnapshot(q, (snapshot) => {
    const messages = snapshot.docs.map(doc => ({
      id: doc.id,
      ...doc.data()
    }));
    callback(messages);
  });
}

// Usage example
const unsubscribe = watchProducts((products) => {
  console.log('In-stock products updated:', products.length, 'items');
});

// Component cleanup
// useEffect(() => {
//   return () => unsubscribe();
// }, []);

Advanced Queries and Indexing

// Complex queries and filtering
import { 
  query, 
  where, 
  orderBy, 
  limit, 
  startAfter, 
  endBefore,
  arrayContains,
  arrayContainsAny,
  increment,
  arrayUnion,
  arrayRemove
} from 'firebase/firestore';

// Compound queries
async function advancedQueries() {
  // Array field queries
  const featuresQuery = query(
    collection(db, 'products'),
    where('features', 'array-contains', 'Bluetooth 5.0')
  );
  
  // Multiple value array queries
  const colorsQuery = query(
    collection(db, 'products'),
    where('specifications.colors', 'array-contains-any', ['black', 'white'])
  );
  
  // Range queries
  const priceRangeQuery = query(
    collection(db, 'products'),
    where('price', '>=', 100),
    where('price', '<=', 500),
    orderBy('price', 'asc')
  );
  
  // Pagination
  let lastVisible = null;
  
  async function getNextPage() {
    let q = query(
      collection(db, 'products'),
      orderBy('metadata.createdAt', 'desc'),
      limit(20)
    );
    
    if (lastVisible) {
      q = query(q, startAfter(lastVisible));
    }
    
    const snapshot = await getDocs(q);
    lastVisible = snapshot.docs[snapshot.docs.length - 1];
    
    return snapshot.docs.map(doc => ({
      id: doc.id,
      ...doc.data()
    }));
  }
  
  return { getNextPage };
}

// Geolocation queries
import { GeoPoint } from 'firebase/firestore';

async function locationBasedQuery() {
  // Create geo point
  const newYorkLocation = new GeoPoint(40.7128, -74.0060);
  
  // Create store document
  await addDoc(collection(db, 'stores'), {
    name: 'Manhattan Store',
    location: newYorkLocation,
    address: '123 5th Ave, New York, NY',
    phoneNumber: '+1-555-123-4567',
    businessHours: {
      open: '09:00',
      close: '21:00'
    }
  });
  
  // Search nearby stores (radius calculation requires additional library)
  const nearbyStores = query(
    collection(db, 'stores'),
    where('location', '!=', null)
  );
  
  return nearbyStores;
}

Transactions and Batch Processing

// Transaction processing
import { runTransaction, writeBatch } from 'firebase/firestore';

// Inventory management transaction
async function purchaseProduct(productId, quantity) {
  try {
    const result = await runTransaction(db, async (transaction) => {
      const productRef = doc(db, 'products', productId);
      const productDoc = await transaction.get(productRef);
      
      if (!productDoc.exists()) {
        throw new Error('Product does not exist');
      }
      
      const productData = productDoc.data();
      const currentStock = productData.availability.quantity;
      
      if (currentStock < quantity) {
        throw new Error('Insufficient stock');
      }
      
      // Reduce inventory
      transaction.update(productRef, {
        'availability.quantity': currentStock - quantity,
        'metadata.updatedAt': serverTimestamp()
      });
      
      // Add order record
      const orderRef = doc(collection(db, 'orders'));
      transaction.set(orderRef, {
        productId: productId,
        quantity: quantity,
        status: 'pending',
        createdAt: serverTimestamp()
      });
      
      return { orderId: orderRef.id, remainingStock: currentStock - quantity };
    });
    
    console.log('Purchase completed:', result);
    return result;
  } catch (error) {
    console.error('Purchase error:', error);
    throw error;
  }
}

// Batch operations
async function batchOperations() {
  const batch = writeBatch(db);
  
  // Bulk price update for multiple products
  const products = ['product1', 'product2', 'product3'];
  
  products.forEach(productId => {
    const docRef = doc(db, 'products', productId);
    batch.update(docRef, {
      price: increment(10), // $10 price increase
      'metadata.updatedAt': serverTimestamp()
    });
  });
  
  // Add sale information
  const saleRef = doc(collection(db, 'sales'));
  batch.set(saleRef, {
    title: 'Summer Sale',
    discount: 0.2,
    startDate: new Date('2024-07-01'),
    endDate: new Date('2024-07-31'),
    products: products
  });
  
  try {
    await batch.commit();
    console.log('Batch operation completed');
  } catch (error) {
    console.error('Batch error:', error);
  }
}

Security Rules and Best Practices

// Security rules example (firestore.rules)
/*
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // Users can only access their own documents
    match /users/{userId} {
      allow read, write: if request.auth != null && request.auth.uid == userId;
    }
    
    // Products are readable by anyone, writable by admins only
    match /products/{productId} {
      allow read: if true;
      allow write: if request.auth != null && 
        exists(/databases/$(database)/documents/admins/$(request.auth.uid));
    }
    
    // Orders accessible by owner and admins only
    match /orders/{orderId} {
      allow read, write: if request.auth != null && 
        (resource.data.userId == request.auth.uid ||
         exists(/databases/$(database)/documents/admins/$(request.auth.uid)));
    }
  }
}
*/

// Client-side authentication integration
import { getAuth, onAuthStateChanged } from 'firebase/auth';

const auth = getAuth();

// Monitor authentication state
onAuthStateChanged(auth, (user) => {
  if (user) {
    console.log('User logged in:', user.uid);
    // Access user-specific data
    setupUserData(user.uid);
  } else {
    console.log('User logged out');
  }
});

// Performance optimization
class FirestoreOptimizer {
  constructor(db) {
    this.db = db;
    this.cache = new Map();
  }
  
  // Cached document retrieval
  async getCachedDoc(collection, id, maxAge = 300000) { // 5 minute cache
    const cacheKey = `${collection}/${id}`;
    const cached = this.cache.get(cacheKey);
    
    if (cached && Date.now() - cached.timestamp < maxAge) {
      return cached.data;
    }
    
    const docSnap = await getDoc(doc(this.db, collection, id));
    const data = docSnap.exists() ? docSnap.data() : null;
    
    this.cache.set(cacheKey, {
      data,
      timestamp: Date.now()
    });
    
    return data;
  }
  
  // Offline fallback
  async getWithFallback(collection, id) {
    try {
      // Get latest data when online
      const docSnap = await getDoc(doc(this.db, collection, id));
      return docSnap.exists() ? docSnap.data() : null;
    } catch (error) {
      console.warn('Online fetch failed, retrieving from cache:', error);
      return this.getCachedDoc(collection, id, Infinity);
    }
  }
}