Firebase - Google Backend-as-a-Service Platform
"Clase de la hoja"
########################################################################################################################################################################################################################################################## Copiar todos los comandos
########################################################################################################################################################################################################################################################## Generar PDF seleccionado/button
■/div titulada
Firebase representa La plataforma integral Backend-as-a-Service (BaaS) de Google, diseñada para acelerar el desarrollo de aplicaciones proporcionando un conjunto de herramientas y servicios basados en la nube. Originalmente desarrollado por Firebase Inc. y adquirido por Google en 2014, Firebase ha evolucionado en una plataforma de desarrollo completa que maneja infraestructura de backend, sincronización de datos en tiempo real, autenticación, hosting, analítica, y mucho más. Firebase ofrece dos soluciones primarias de base de datos: la base de datos original en tiempo real y la tienda más avanzada de Cloud Firestore, ambas diseñadas para apoyar aplicaciones en tiempo real con capacidades offline y sincronización perfecta entre múltiples clientes.
Comienzo con Firebase
Configuración y configuración del proyecto
// Install Firebase CLI
npm install -g firebase-tools
// Login to Firebase
firebase login
// Initialize Firebase project
firebase init
// Install Firebase SDK for web
npm install firebase
// Initialize Firebase in your application
import \\\\{ initializeApp \\\\} from 'firebase/app';
import \\\\{ getFirestore \\\\} from 'firebase/firestore';
import \\\\{ getAuth \\\\} from 'firebase/auth';
import \\\\{ getStorage \\\\} from 'firebase/storage';
const firebaseConfig = \\\\{
apiKey: "your-api-key",
authDomain: "your-project.firebaseapp.com",
databaseURL: "https://your-project-default-rtdb.firebaseio.com",
projectId: "your-project-id",
storageBucket: "your-project.appspot.com",
messagingSenderId: "123456789",
appId: "your-app-id"
\\\\};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
// Initialize services
const db = getFirestore(app);
const auth = getAuth(app);
const storage = getStorage(app);
export \\\\{ db, auth, storage \\\\};
Firebase Console and Project Management
// Firebase project structure
// - Authentication: User management and sign-in
// - Firestore Database: NoSQL document database
// - Realtime Database: JSON tree database
// - Storage: File storage service
// - Hosting: Web hosting service
// - Functions: Serverless functions
// - Analytics: App usage analytics
// - Performance: App performance monitoring
// Environment configuration
// Development
const devConfig = \\\\{
apiKey: "dev-api-key",
authDomain: "dev-project.firebaseapp.com",
projectId: "dev-project-id"
\\\\};
// Production
const prodConfig = \\\\{
apiKey: "prod-api-key",
authDomain: "prod-project.firebaseapp.com",
projectId: "prod-project-id"
\\\\};
const config = process.env.NODE_ENV === 'production' ? prodConfig : devConfig;
const app = initializeApp(config);
Cloud Firestore (Recomendado)
Estructura y colecciones de bases de datos
// Firestore data model: Collections -> Documents -> Fields
// Collections contain documents
// Documents contain fields and subcollections
// Documents are identified by unique IDs
import \\\\{
collection,
doc,
addDoc,
setDoc,
getDoc,
getDocs,
updateDoc,
deleteDoc,
query,
where,
orderBy,
limit,
onSnapshot
\\\\} from 'firebase/firestore';
// Reference to a collection
const usersRef = collection(db, 'users');
// Reference to a specific document
const userRef = doc(db, 'users', 'user123');
// Reference to a subcollection
const postsRef = collection(db, 'users', 'user123', 'posts');
// Create document with auto-generated ID
const createUser = async (userData) => \\\\{
try \\\\{
const docRef = await addDoc(collection(db, 'users'), \\\\{
name: userData.name,
email: userData.email,
createdAt: new Date(),
isActive: true,
profile: \\\\{
bio: userData.bio||'',
avatar: userData.avatar||'',
preferences: \\\\{
theme: 'light',
notifications: true
\\\\}
\\\\}
\\\\});
console.log('Document written with ID: ', docRef.id);
return docRef.id;
\\\\} catch (error) \\\\{
console.error('Error adding document: ', error);
throw error;
\\\\}
\\\\};
// Create document with custom ID
const createUserWithId = async (userId, userData) => \\\\{
try \\\\{
await setDoc(doc(db, 'users', userId), \\\\{
name: userData.name,
email: userData.email,
createdAt: new Date(),
isActive: true
\\\\});
console.log('Document created with custom ID: ', userId);
\\\\} catch (error) \\\\{
console.error('Error creating document: ', error);
throw error;
\\\\}
\\\\};
CRUD Operaciones
// CREATE - Add documents
const addPost = async (userId, postData) => \\\\{
try \\\\{
const postRef = await addDoc(collection(db, 'users', userId, 'posts'), \\\\{
title: postData.title,
content: postData.content,
tags: postData.tags||[],
publishedAt: new Date(),
isPublished: false,
likes: 0,
comments: []
\\\\});
return postRef.id;
\\\\} catch (error) \\\\{
console.error('Error adding post: ', error);
throw error;
\\\\}
\\\\};
// READ - Get single document
const getUser = async (userId) => \\\\{
try \\\\{
const docSnap = await getDoc(doc(db, 'users', userId));
if (docSnap.exists()) \\\\{
return \\\\{ id: docSnap.id, ...docSnap.data() \\\\};
\\\\} else \\\\{
console.log('No such document!');
return null;
\\\\}
\\\\} catch (error) \\\\{
console.error('Error getting document: ', error);
throw error;
\\\\}
\\\\};
// READ - Get multiple documents
const getAllUsers = async () => \\\\{
try \\\\{
const querySnapshot = await getDocs(collection(db, 'users'));
const users = [];
querySnapshot.forEach((doc) => \\\\{
users.push(\\\\{ id: doc.id, ...doc.data() \\\\});
\\\\});
return users;
\\\\} catch (error) \\\\{
console.error('Error getting documents: ', error);
throw error;
\\\\}
\\\\};
// UPDATE - Update document
const updateUser = async (userId, updates) => \\\\{
try \\\\{
const userRef = doc(db, 'users', userId);
await updateDoc(userRef, \\\\{
...updates,
updatedAt: new Date()
\\\\});
console.log('Document updated successfully');
\\\\} catch (error) \\\\{
console.error('Error updating document: ', error);
throw error;
\\\\}
\\\\};
// UPDATE - Update nested fields
const updateUserProfile = async (userId, profileUpdates) => \\\\{
try \\\\{
const userRef = doc(db, 'users', userId);
await updateDoc(userRef, \\\\{
'profile.bio': profileUpdates.bio,
'profile.avatar': profileUpdates.avatar,
'profile.preferences.theme': profileUpdates.theme,
updatedAt: new Date()
\\\\});
\\\\} catch (error) \\\\{
console.error('Error updating profile: ', error);
throw error;
\\\\}
\\\\};
// DELETE - Delete document
const deleteUser = async (userId) => \\\\{
try \\\\{
await deleteDoc(doc(db, 'users', userId));
console.log('Document deleted successfully');
\\\\} catch (error) \\\\{
console.error('Error deleting document: ', error);
throw error;
\\\\}
\\\\};
Consultas y Filtros
import \\\\{
query,
where,
orderBy,
limit,
startAt,
endAt,
startAfter,
endBefore
\\\\} from 'firebase/firestore';
// Simple queries
const getActiveUsers = async () => \\\\{
const q = query(
collection(db, 'users'),
where('isActive', '==', true)
);
const querySnapshot = await getDocs(q);
return querySnapshot.docs.map(doc => (\\\\{ id: doc.id, ...doc.data() \\\\}));
\\\\};
// Compound queries
const getRecentActivePosts = async () => \\\\{
const q = query(
collection(db, 'posts'),
where('isPublished', '==', true),
where('publishedAt', '>', new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)),
orderBy('publishedAt', 'desc'),
limit(10)
);
const querySnapshot = await getDocs(q);
return querySnapshot.docs.map(doc => (\\\\{ id: doc.id, ...doc.data() \\\\}));
\\\\};
// Array queries
const getPostsByTags = async (tags) => \\\\{
const q = query(
collection(db, 'posts'),
where('tags', 'array-contains-any', tags)
);
const querySnapshot = await getDocs(q);
return querySnapshot.docs.map(doc => (\\\\{ id: doc.id, ...doc.data() \\\\}));
\\\\};
// Range queries
const getUsersByAgeRange = async (minAge, maxAge) => \\\\{
const q = query(
collection(db, 'users'),
where('age', '>=', minAge),
where('age', '<=', maxAge),
orderBy('age')
);
const querySnapshot = await getDocs(q);
return querySnapshot.docs.map(doc => (\\\\{ id: doc.id, ...doc.data() \\\\}));
\\\\};
// Pagination
const getPaginatedUsers = async (pageSize = 10, lastDoc = null) => \\\\{
let q = query(
collection(db, 'users'),
orderBy('createdAt', 'desc'),
limit(pageSize)
);
if (lastDoc) \\\\{
q = query(
collection(db, 'users'),
orderBy('createdAt', 'desc'),
startAfter(lastDoc),
limit(pageSize)
);
\\\\}
const querySnapshot = await getDocs(q);
const users = querySnapshot.docs.map(doc => (\\\\{ id: doc.id, ...doc.data() \\\\}));
const lastVisible = querySnapshot.docs[querySnapshot.docs.length - 1];
return \\\\{ users, lastDoc: lastVisible, hasMore: users.length === pageSize \\\\};
\\\\};
// Text search (limited - consider Algolia for full-text search)
const searchUsersByName = async (searchTerm) => \\\\{
const q = query(
collection(db, 'users'),
where('name', '>=', searchTerm),
where('name', '<=', searchTerm + '\uf8ff'),
orderBy('name'),
limit(10)
);
const querySnapshot = await getDocs(q);
return querySnapshot.docs.map(doc => (\\\\{ id: doc.id, ...doc.data() \\\\}));
\\\\};
Escuchadores en tiempo real
// Real-time listener for single document
const subscribeToUser = (userId, callback) => \\\\{
const userRef = doc(db, 'users', userId);
const unsubscribe = onSnapshot(userRef, (doc) => \\\\{
if (doc.exists()) \\\\{
callback(\\\\{ id: doc.id, ...doc.data() \\\\});
\\\\} else \\\\{
callback(null);
\\\\}
\\\\}, (error) => \\\\{
console.error('Error listening to user: ', error);
\\\\});
return unsubscribe; // Call this function to stop listening
\\\\};
// Real-time listener for collection
const subscribeToActiveUsers = (callback) => \\\\{
const q = query(
collection(db, 'users'),
where('isActive', '==', true),
orderBy('createdAt', 'desc')
);
const unsubscribe = onSnapshot(q, (querySnapshot) => \\\\{
const users = [];
querySnapshot.forEach((doc) => \\\\{
users.push(\\\\{ id: doc.id, ...doc.data() \\\\});
\\\\});
callback(users);
\\\\}, (error) => \\\\{
console.error('Error listening to users: ', error);
\\\\});
return unsubscribe;
\\\\};
// Listen to document changes with metadata
const subscribeToUserWithMetadata = (userId, callback) => \\\\{
const userRef = doc(db, 'users', userId);
const unsubscribe = onSnapshot(userRef, \\\\{ includeMetadataChanges: true \\\\}, (doc) => \\\\{
const source = doc.metadata.hasPendingWrites ? 'Local' : 'Server';
const data = doc.exists() ? \\\\{ id: doc.id, ...doc.data() \\\\} : null;
callback(\\\\{ data, source, fromCache: doc.metadata.fromCache \\\\});
\\\\});
return unsubscribe;
\\\\};
// React hook example for real-time data
const useUser = (userId) => \\\\{
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => \\\\{
if (!userId) \\\\{
setUser(null);
setLoading(false);
return;
\\\\}
const unsubscribe = subscribeToUser(userId, (userData) => \\\\{
setUser(userData);
setLoading(false);
setError(null);
\\\\});
return () => unsubscribe();
\\\\}, [userId]);
return \\\\{ user, loading, error \\\\};
\\\\};
Transacciones y operaciones de bastón
import \\\\{
runTransaction,
writeBatch,
increment,
arrayUnion,
arrayRemove,
serverTimestamp
\\\\} from 'firebase/firestore';
// Transaction example - Transfer points between users
const transferPoints = async (fromUserId, toUserId, points) => \\\\{
try \\\\{
await runTransaction(db, async (transaction) => \\\\{
const fromUserRef = doc(db, 'users', fromUserId);
const toUserRef = doc(db, 'users', toUserId);
const fromUserDoc = await transaction.get(fromUserRef);
const toUserDoc = await transaction.get(toUserRef);
if (!fromUserDoc.exists()||!toUserDoc.exists()) \\\\{
throw new Error('One or both users do not exist');
\\\\}
const fromUserPoints = fromUserDoc.data().points||0;
if (fromUserPoints < points) \\\\{
throw new Error('Insufficient points');
\\\\}
transaction.update(fromUserRef, \\\\{
points: fromUserPoints - points,
updatedAt: serverTimestamp()
\\\\});
transaction.update(toUserRef, \\\\{
points: increment(points),
updatedAt: serverTimestamp()
\\\\});
\\\\});
console.log('Points transferred successfully');
\\\\} catch (error) \\\\{
console.error('Transaction failed: ', error);
throw error;
\\\\}
\\\\};
// Batch operations
const batchUpdateUsers = async (userUpdates) => \\\\{
const batch = writeBatch(db);
userUpdates.forEach((\\\\{ userId, updates \\\\}) => \\\\{
const userRef = doc(db, 'users', userId);
batch.update(userRef, \\\\{
...updates,
updatedAt: serverTimestamp()
\\\\});
\\\\});
try \\\\{
await batch.commit();
console.log('Batch update completed');
\\\\} catch (error) \\\\{
console.error('Batch update failed: ', error);
throw error;
\\\\}
\\\\};
// Array operations
const addTagToPost = async (postId, tag) => \\\\{
const postRef = doc(db, 'posts', postId);
await updateDoc(postRef, \\\\{
tags: arrayUnion(tag),
updatedAt: serverTimestamp()
\\\\});
\\\\};
const removeTagFromPost = async (postId, tag) => \\\\{
const postRef = doc(db, 'posts', postId);
await updateDoc(postRef, \\\\{
tags: arrayRemove(tag),
updatedAt: serverTimestamp()
\\\\});
\\\\};
// Increment/decrement operations
const likePost = async (postId) => \\\\{
const postRef = doc(db, 'posts', postId);
await updateDoc(postRef, \\\\{
likes: increment(1),
updatedAt: serverTimestamp()
\\\\});
\\\\};
Base de datos en tiempo real
Estructura de bases de datos y referencias
import \\\\{
getDatabase,
ref,
set,
get,
push,
update,
remove,
on,
off,
child,
orderByChild,
orderByKey,
orderByValue,
limitToFirst,
limitToLast,
startAt,
endAt,
equalTo
\\\\} from 'firebase/database';
// Initialize Realtime Database
const rtdb = getDatabase();
// Database structure (JSON tree)
// \\\\{
// "users": \\\\{
// "user1": \\\\{
// "name": "John Doe",
// "email": "john@example.com",
// "posts": \\\\{
// "post1": true,
// "post2": true
// \\\\}
// \\\\}
// \\\\},
// "posts": \\\\{
// "post1": \\\\{
// "title": "First Post",
// "content": "Hello World",
// "author": "user1",
// "timestamp": 1642694400000
// \\\\}
// \\\\}
// \\\\}
// Create references
const usersRef = ref(rtdb, 'users');
const userRef = ref(rtdb, 'users/user1');
const postsRef = ref(rtdb, 'posts');
CRUD Operaciones en la base de datos en tiempo real
// CREATE/UPDATE - Set data
const createUser = async (userId, userData) => \\\\{
try \\\\{
await set(ref(rtdb, `users/$\\{userId\\}`), \\\\{
name: userData.name,
email: userData.email,
createdAt: Date.now(),
isActive: true
\\\\});
console.log('User created successfully');
\\\\} catch (error) \\\\{
console.error('Error creating user: ', error);
throw error;
\\\\}
\\\\};
// CREATE - Push data (auto-generated key)
const addPost = async (postData) => \\\\{
try \\\\{
const newPostRef = push(ref(rtdb, 'posts'));
await set(newPostRef, \\\\{
title: postData.title,
content: postData.content,
author: postData.author,
timestamp: Date.now(),
likes: 0
\\\\});
return newPostRef.key;
\\\\} catch (error) \\\\{
console.error('Error adding post: ', error);
throw error;
\\\\}
\\\\};
// READ - Get data once
const getUser = async (userId) => \\\\{
try \\\\{
const snapshot = await get(ref(rtdb, `users/$\\{userId\\}`));
if (snapshot.exists()) \\\\{
return \\\\{ id: userId, ...snapshot.val() \\\\};
\\\\} else \\\\{
console.log('No data available');
return null;
\\\\}
\\\\} catch (error) \\\\{
console.error('Error getting user: ', error);
throw error;
\\\\}
\\\\};
// READ - Get all users
const getAllUsers = async () => \\\\{
try \\\\{
const snapshot = await get(ref(rtdb, 'users'));
if (snapshot.exists()) \\\\{
const users = [];
snapshot.forEach((childSnapshot) => \\\\{
users.push(\\\\{
id: childSnapshot.key,
...childSnapshot.val()
\\\\});
\\\\});
return users;
\\\\} else \\\\{
return [];
\\\\}
\\\\} catch (error) \\\\{
console.error('Error getting users: ', error);
throw error;
\\\\}
\\\\};
// UPDATE - Update specific fields
const updateUser = async (userId, updates) => \\\\{
try \\\\{
await update(ref(rtdb, `users/$\\{userId\\}`), \\\\{
...updates,
updatedAt: Date.now()
\\\\});
console.log('User updated successfully');
\\\\} catch (error) \\\\{
console.error('Error updating user: ', error);
throw error;
\\\\}
\\\\};
// DELETE - Remove data
const deleteUser = async (userId) => \\\\{
try \\\\{
await remove(ref(rtdb, `users/$\\{userId\\}`));
console.log('User deleted successfully');
\\\\} catch (error) \\\\{
console.error('Error deleting user: ', error);
throw error;
\\\\}
\\\\};
Escuchadores y consultas en tiempo real
// Real-time listener
const subscribeToUser = (userId, callback) => \\\\{
const userRef = ref(rtdb, `users/$\\{userId\\}`);
const unsubscribe = on(userRef, 'value', (snapshot) => \\\\{
const data = snapshot.exists() ? \\\\{ id: userId, ...snapshot.val() \\\\} : null;
callback(data);
\\\\}, (error) => \\\\{
console.error('Error listening to user: ', error);
\\\\});
return () => off(userRef, 'value', unsubscribe);
\\\\};
// Listen to child events
const subscribeToUserPosts = (userId, callbacks) => \\\\{
const userPostsRef = ref(rtdb, `users/$\\{userId\\}/posts`);
const childAddedListener = on(userPostsRef, 'child_added', callbacks.onAdded);
const childChangedListener = on(userPostsRef, 'child_changed', callbacks.onChanged);
const childRemovedListener = on(userPostsRef, 'child_removed', callbacks.onRemoved);
return () => \\\\{
off(userPostsRef, 'child_added', childAddedListener);
off(userPostsRef, 'child_changed', childChangedListener);
off(userPostsRef, 'child_removed', childRemovedListener);
\\\\};
\\\\};
// Queries with ordering and filtering
const getTopPosts = async (limit = 10) => \\\\{
try \\\\{
const topPostsQuery = query(
ref(rtdb, 'posts'),
orderByChild('likes'),
limitToLast(limit)
);
const snapshot = await get(topPostsQuery);
const posts = [];
snapshot.forEach((childSnapshot) => \\\\{
posts.unshift(\\\\{
id: childSnapshot.key,
...childSnapshot.val()
\\\\});
\\\\});
return posts;
\\\\} catch (error) \\\\{
console.error('Error getting top posts: ', error);
throw error;
\\\\}
\\\\};
// Range queries
const getPostsByDateRange = async (startDate, endDate) => \\\\{
try \\\\{
const dateRangeQuery = query(
ref(rtdb, 'posts'),
orderByChild('timestamp'),
startAt(startDate),
endAt(endDate)
);
const snapshot = await get(dateRangeQuery);
const posts = [];
snapshot.forEach((childSnapshot) => \\\\{
posts.push(\\\\{
id: childSnapshot.key,
...childSnapshot.val()
\\\\});
\\\\});
return posts;
\\\\} catch (error) \\\\{
console.error('Error getting posts by date range: ', error);
throw error;
\\\\}
\\\\};
// Equal to query
const getUsersByStatus = async (status) => \\\\{
try \\\\{
const statusQuery = query(
ref(rtdb, 'users'),
orderByChild('status'),
equalTo(status)
);
const snapshot = await get(statusQuery);
const users = [];
snapshot.forEach((childSnapshot) => \\\\{
users.push(\\\\{
id: childSnapshot.key,
...childSnapshot.val()
\\\\});
\\\\});
return users;
\\\\} catch (error) \\\\{
console.error('Error getting users by status: ', error);
throw error;
\\\\}
\\\\};
Firebase Authentication
Configuración de autenticación y métodos
import \\\\{
getAuth,
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
signInWithPopup,
GoogleAuthProvider,
FacebookAuthProvider,
TwitterAuthProvider,
signOut,
onAuthStateChanged,
updateProfile,
updatePassword,
sendPasswordResetEmail,
deleteUser
\\\\} from 'firebase/auth';
const auth = getAuth();
// Email/Password Authentication
const signUpWithEmail = async (email, password, displayName) => \\\\{
try \\\\{
const userCredential = await createUserWithEmailAndPassword(auth, email, password);
const user = userCredential.user;
// Update user profile
await updateProfile(user, \\\\{
displayName: displayName
\\\\});
// Create user document in Firestore
await setDoc(doc(db, 'users', user.uid), \\\\{
email: user.email,
displayName: displayName,
createdAt: new Date(),
isActive: true
\\\\});
return user;
\\\\} catch (error) \\\\{
console.error('Error signing up: ', error);
throw error;
\\\\}
\\\\};
const signInWithEmail = async (email, password) => \\\\{
try \\\\{
const userCredential = await signInWithEmailAndPassword(auth, email, password);
return userCredential.user;
\\\\} catch (error) \\\\{
console.error('Error signing in: ', error);
throw error;
\\\\}
\\\\};
// Social Authentication
const signInWithGoogle = async () => \\\\{
try \\\\{
const provider = new GoogleAuthProvider();
provider.addScope('profile');
provider.addScope('email');
const result = await signInWithPopup(auth, provider);
const user = result.user;
// Check if user document exists, create if not
const userDoc = await getDoc(doc(db, 'users', user.uid));
if (!userDoc.exists()) \\\\{
await setDoc(doc(db, 'users', user.uid), \\\\{
email: user.email,
displayName: user.displayName,
photoURL: user.photoURL,
provider: 'google',
createdAt: new Date(),
isActive: true
\\\\});
\\\\}
return user;
\\\\} catch (error) \\\\{
console.error('Error signing in with Google: ', error);
throw error;
\\\\}
\\\\};
// Authentication state observer
const useAuthState = () => \\\\{
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => \\\\{
const unsubscribe = onAuthStateChanged(auth, (user) => \\\\{
setUser(user);
setLoading(false);
\\\\});
return () => unsubscribe();
\\\\}, []);
return \\\\{ user, loading \\\\};
\\\\};
// Sign out
const signOutUser = async () => \\\\{
try \\\\{
await signOut(auth);
console.log('User signed out successfully');
\\\\} catch (error) \\\\{
console.error('Error signing out: ', error);
throw error;
\\\\}
\\\\};
// Password reset
const resetPassword = async (email) => \\\\{
try \\\\{
await sendPasswordResetEmail(auth, email);
console.log('Password reset email sent');
\\\\} catch (error) \\\\{
console.error('Error sending password reset email: ', error);
throw error;
\\\\}
\\\\};
// Update user profile
const updateUserProfile = async (updates) => \\\\{
try \\\\{
const user = auth.currentUser;
if (user) \\\\{
await updateProfile(user, updates);
// Also update Firestore document
await updateDoc(doc(db, 'users', user.uid), \\\\{
...updates,
updatedAt: new Date()
\\\\});
\\\\}
\\\\} catch (error) \\\\{
console.error('Error updating profile: ', error);
throw error;
\\\\}
\\\\};
Normas de seguridad
// Firestore Security Rules
// rules_version = '2';
// service cloud.firestore \\\\{
// match /databases/\\\\{database\\\\}/documents \\\\{
// // Users can read and write their own data
// match /users/\\\\{userId\\\\} \\\\{
// allow read, write: if request.auth != null && request.auth.uid == userId;
// \\\\}
//
// // Posts are readable by all authenticated users
// // Only the author can write/update/delete
// match /posts/\\\\{postId\\\\} \\\\{
// allow read: if request.auth != null;
// allow create: if request.auth != null &&
// request.auth.uid == resource.data.authorId;
// allow update, delete: if request.auth != null &&
// request.auth.uid == resource.data.authorId;
// \\\\}
//
// // Comments can be created by authenticated users
// // Only the author can update/delete their comments
// match /posts/\\\\{postId\\\\}/comments/\\\\{commentId\\\\} \\\\{
// allow read: if request.auth != null;
// allow create: if request.auth != null;
// allow update, delete: if request.auth != null &&
// request.auth.uid == resource.data.authorId;
// \\\\}
// \\\\}
// \\\\}
// Realtime Database Security Rules
// \\\\{
// "rules": \\\\{
// "users": \\\\{
// "$uid": \\\\{
// ".read": "$uid === auth.uid",
// ".write": "$uid === auth.uid"
// \\\\}
// \\\\},
// "posts": \\\\{
// ".read": "auth != null",
// "$postId": \\\\{
// ".write": "auth != null && (
// !data.exists()||
// data.child('authorId').val() === auth.uid
// )"
// \\\\}
// \\\\}
// \\\\}
// \\\\}
// Custom claims for role-based access
const setUserRole = async (uid, role) => \\\\{
// This would be done in a Cloud Function with Admin SDK
// admin.auth().setCustomUserClaims(uid, \\\\{ role: role \\\\});
\\\\};
// Check user role in security rules
// allow write: if request.auth.token.role == 'admin';
Almacenamiento de bomberos
Carga de archivos y administración
import \\\\{
getStorage,
ref as storageRef,
uploadBytes,
uploadBytesResumable,
getDownloadURL,
deleteObject,
listAll,
getMetadata,
updateMetadata
\\\\} from 'firebase/storage';
const storage = getStorage();
// Upload file
const uploadFile = async (file, path) => \\\\{
try \\\\{
const fileRef = storageRef(storage, path);
const snapshot = await uploadBytes(fileRef, file);
const downloadURL = await getDownloadURL(snapshot.ref);
console.log('File uploaded successfully');
return \\\\{ downloadURL, fullPath: snapshot.ref.fullPath \\\\};
\\\\} catch (error) \\\\{
console.error('Error uploading file: ', error);
throw error;
\\\\}
\\\\};
// Upload with progress tracking
const uploadFileWithProgress = (file, path, onProgress) => \\\\{
return new Promise((resolve, reject) => \\\\{
const fileRef = storageRef(storage, path);
const uploadTask = uploadBytesResumable(fileRef, file);
uploadTask.on('state_changed',
(snapshot) => \\\\{
const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
onProgress(progress);
\\\\},
(error) => \\\\{
console.error('Upload error: ', error);
reject(error);
\\\\},
async () => \\\\{
try \\\\{
const downloadURL = await getDownloadURL(uploadTask.snapshot.ref);
resolve(\\\\{ downloadURL, fullPath: uploadTask.snapshot.ref.fullPath \\\\});
\\\\} catch (error) \\\\{
reject(error);
\\\\}
\\\\}
);
\\\\});
\\\\};
// Upload user avatar
const uploadUserAvatar = async (userId, file) => \\\\{
try \\\\{
const path = `avatars/$\\{userId\\}/$\\{file.name\\}`;
const result = await uploadFile(file, path);
// Update user profile with avatar URL
await updateDoc(doc(db, 'users', userId), \\\\{
photoURL: result.downloadURL,
updatedAt: new Date()
\\\\});
return result.downloadURL;
\\\\} catch (error) \\\\{
console.error('Error uploading avatar: ', error);
throw error;
\\\\}
\\\\};
// Delete file
const deleteFile = async (filePath) => \\\\{
try \\\\{
const fileRef = storageRef(storage, filePath);
await deleteObject(fileRef);
console.log('File deleted successfully');
\\\\} catch (error) \\\\{
console.error('Error deleting file: ', error);
throw error;
\\\\}
\\\\};
// List files in directory
const listFiles = async (path) => \\\\{
try \\\\{
const listRef = storageRef(storage, path);
const result = await listAll(listRef);
const files = await Promise.all(
result.items.map(async (itemRef) => \\\\{
const url = await getDownloadURL(itemRef);
const metadata = await getMetadata(itemRef);
return \\\\{
name: itemRef.name,
fullPath: itemRef.fullPath,
downloadURL: url,
size: metadata.size,
contentType: metadata.contentType,
timeCreated: metadata.timeCreated
\\\\};
\\\\})
);
return files;
\\\\} catch (error) \\\\{
console.error('Error listing files: ', error);
throw error;
\\\\}
\\\\};
Funciones de la nube de fuego
Desarrollo de funciones y despliegue
// Install Firebase Functions
// npm install -g firebase-tools
// firebase init functions
// functions/index.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
// HTTP Cloud Function
exports.helloWorld = functions.https.onRequest((request, response) => \\\\{
response.send('Hello from Firebase!');
\\\\});
// Firestore Trigger
exports.onUserCreate = functions.firestore
.document('users/\\\\{userId\\\\}')
.onCreate(async (snap, context) => \\\\{
const userData = snap.data();
const userId = context.params.userId;
// Send welcome email
console.log(`New user created: $\\{userData.email\\}`);
// Create user stats document
await admin.firestore().collection('userStats').doc(userId).set(\\\\{
postsCount: 0,
likesReceived: 0,
joinedAt: admin.firestore.FieldValue.serverTimestamp()
\\\\});
\\\\});
// Scheduled Function
exports.dailyCleanup = functions.pubsub
.schedule('0 2 * * *') // Every day at 2 AM
.timeZone('America/New_York')
.onRun(async (context) => \\\\{
// Clean up old data
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - 30);
const oldPosts = await admin.firestore()
.collection('posts')
.where('createdAt', '<', cutoffDate)
.where('isDeleted', '==', true)
.get();
const batch = admin.firestore().batch();
oldPosts.docs.forEach(doc => \\\\{
batch.delete(doc.ref);
\\\\});
await batch.commit();
console.log(`Deleted $\\{oldPosts.size\\} old posts`);
\\\\});
// Callable Function
exports.addAdminRole = functions.https.onCall(async (data, context) => \\\\{
// Check if user is authenticated and is admin
if (!context.auth) \\\\{
throw new functions.https.HttpsError('unauthenticated', 'User must be authenticated');
\\\\}
if (!context.auth.token.admin) \\\\{
throw new functions.https.HttpsError('permission-denied', 'User must be admin');
\\\\}
try \\\\{
await admin.auth().setCustomUserClaims(data.uid, \\\\{ admin: true \\\\});
return \\\\{ message: 'Admin role added successfully' \\\\};
\\\\} catch (error) \\\\{
throw new functions.https.HttpsError('internal', 'Error adding admin role');
\\\\}
\\\\});
// Deploy functions
// firebase deploy --only functions
Optimización del rendimiento y mejores prácticas
Modelado de datos Buenas prácticas
// Firestore data modeling principles
// 1. Denormalization for read efficiency
const createPostWithAuthor = async (postData, authorData) => \\\\{
const postRef = await addDoc(collection(db, 'posts'), \\\\{
title: postData.title,
content: postData.content,
// Denormalize author data for efficient reads
author: \\\\{
id: authorData.id,
name: authorData.name,
avatar: authorData.avatar
\\\\},
createdAt: new Date(),
likes: 0,
commentsCount: 0
\\\\});
return postRef.id;
\\\\};
// 2. Use subcollections for one-to-many relationships
const addCommentToPost = async (postId, commentData) => \\\\{
const commentRef = await addDoc(
collection(db, 'posts', postId, 'comments'),
\\\\{
content: commentData.content,
author: commentData.author,
createdAt: new Date(),
likes: 0
\\\\}
);
// Update comment count in parent post
await updateDoc(doc(db, 'posts', postId), \\\\{
commentsCount: increment(1)
\\\\});
return commentRef.id;
\\\\};
// 3. Use arrays for small lists, subcollections for large lists
const addTagToPost = async (postId, tag) => \\\\{
await updateDoc(doc(db, 'posts', postId), \\\\{
tags: arrayUnion(tag)
\\\\});
\\\\};
// 4. Optimize for your queries
// Create composite indexes for compound queries
// Index: collection: posts, fields: isPublished ASC, createdAt DESC
const getPublishedPosts = async () => \\\\{
const q = query(
collection(db, 'posts'),
where('isPublished', '==', true),
orderBy('createdAt', 'desc'),
limit(20)
);
return await getDocs(q);
\\\\};
Caching y Offline Support
import \\\\{ enableNetwork, disableNetwork \\\\} from 'firebase/firestore';
// Enable offline persistence
import \\\\{ initializeFirestore, persistentLocalCache \\\\} from 'firebase/firestore';
const db = initializeFirestore(app, \\\\{
localCache: persistentLocalCache()
\\\\});
// Handle online/offline state
const handleNetworkChange = async (isOnline) => \\\\{
try \\\\{
if (isOnline) \\\\{
await enableNetwork(db);
console.log('Network enabled');
\\\\} else \\\\{
await disableNetwork(db);
console.log('Network disabled');
\\\\}
\\\\} catch (error) \\\\{
console.error('Error changing network state: ', error);
\\\\}
\\\\};
// Listen for network changes
window.addEventListener('online', () => handleNetworkChange(true));
window.addEventListener('offline', () => handleNetworkChange(false));
// Cache management
const getCachedData = async (docRef) => \\\\{
try \\\\{
// Try to get from cache first
const cachedDoc = await getDoc(docRef, \\\\{ source: 'cache' \\\\});
if (cachedDoc.exists()) \\\\{
return \\\\{ data: cachedDoc.data(), fromCache: true \\\\};
\\\\}
\\\\} catch (error) \\\\{
console.log('No cached data available');
\\\\}
// Fallback to server
const serverDoc = await getDoc(docRef, \\\\{ source: 'server' \\\\});
return \\\\{ data: serverDoc.exists() ? serverDoc.data() : null, fromCache: false \\\\};
\\\\};
Vigilancia de la seguridad y el desempeño
// Performance monitoring
import \\\\{ getPerformance, trace \\\\} from 'firebase/performance';
const perf = getPerformance(app);
// Custom trace
const measureDatabaseOperation = async (operation) => \\\\{
const customTrace = trace(perf, 'database_operation');
customTrace.start();
try \\\\{
const result = await operation();
customTrace.putAttribute('success', 'true');
return result;
\\\\} catch (error) \\\\{
customTrace.putAttribute('success', 'false');
customTrace.putAttribute('error', error.message);
throw error;
\\\\} finally \\\\{
customTrace.stop();
\\\\}
\\\\};
// Usage
const getUserWithTrace = async (userId) => \\\\{
return await measureDatabaseOperation(async () => \\\\{
return await getDoc(doc(db, 'users', userId));
\\\\});
\\\\};
// Analytics
import \\\\{ getAnalytics, logEvent \\\\} from 'firebase/analytics';
const analytics = getAnalytics(app);
// Log custom events
const logUserAction = (action, parameters = \\\\{\\\\}) => \\\\{
logEvent(analytics, action, \\\\{
...parameters,
timestamp: Date.now()
\\\\});
\\\\};
// Usage
logUserAction('post_created', \\\\{
post_id: 'post123',
category: 'technology'
\\\\});
La amplia gama de servicios de Firebase ofrece a los desarrolladores una poderosa plataforma para construir aplicaciones modernas y escalables con capacidades en tiempo real, autenticación robusta y soporte offline sin problemas. Su integración con Google Cloud Platform y el amplio soporte SDK en múltiples plataformas hace que sea una excelente opción para el desarrollo rápido de aplicaciones, desde prototipos simples a aplicaciones complejas y listas de producción que sirven a millones de usuarios.