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);
}
}
}