¿Qué es Cloud Firestore?
Cloud Firestore es la base de datos NoSQL en la nube de Firebase, flexible y escalable, que almacena y sincroniza datos para desarrollo del lado del cliente y del servidor. Mantiene los datos sincronizados en tiempo real mediante listeners y ofrece soporte offline para que tu app funcione incluso sin conexión.
A diferencia de las bases de datos relacionales tradicionales, Firestore organiza los datos en documentos y colecciones, lo que facilita su uso y permite estructuras de datos flexibles.
📚 Modelo de datos de Firestore
- 📄 Documentos: Registros ligeros con campos y valores (como objetos JSON).
- 📁 Colecciones: Contenedores de documentos. Una colección solo contiene documentos.
- 🔗 Subcolecciones: Colecciones dentro de documentos, para estructuras anidadas.
- 🔍 Referencias: Punteros a documentos o colecciones en tu base de datos.
Configurar Firestore
Inicializa Firestore en tu aplicación:
import { initializeApp } from 'firebase/app';
import { getFirestore } from 'firebase/firestore';
const firebaseConfig = {
// Your config here
};
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
export { db };
Agregar documentos
Hay varias formas de añadir documentos en Firestore:
Agregar con ID autogenerado
import { collection, addDoc } from 'firebase/firestore';
async function addUser(userData) {
try {
const docRef = await addDoc(collection(db, 'users'), {
name: userData.name,
email: userData.email,
createdAt: new Date(),
role: 'user'
});
console.log('Document written with ID:', docRef.id);
return docRef.id;
} catch (error) {
console.error('Error adding document:', error);
throw error;
}
}
Crear con ID personalizado
import { doc, setDoc } from 'firebase/firestore';
async function createUserProfile(userId, profileData) {
try {
await setDoc(doc(db, 'users', userId), {
name: profileData.name,
email: profileData.email,
bio: profileData.bio,
updatedAt: new Date()
});
console.log('Profile created for user:', userId);
} catch (error) {
console.error('Error creating profile:', error);
}
}
// Merge with existing data instead of overwriting
await setDoc(doc(db, 'users', userId), {
lastLogin: new Date()
}, { merge: true });
Leer documentos
Obtén documentos individuales o colecciones completas:
Obtener un documento
import { doc, getDoc } from 'firebase/firestore';
async function getUser(userId) {
const docRef = doc(db, 'users', userId);
const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
console.log('User data:', docSnap.data());
return { id: docSnap.id, ...docSnap.data() };
} else {
console.log('No such document!');
return null;
}
}
Obtener todos los documentos de una colección
import { collection, getDocs } from 'firebase/firestore';
async function getAllUsers() {
const querySnapshot = await getDocs(collection(db, 'users'));
const users = [];
querySnapshot.forEach((doc) => {
users.push({ id: doc.id, ...doc.data() });
});
return users;
}
Consultar documentos
Filtra y ordena documentos con consultas:
import {
collection,
query,
where,
orderBy,
limit,
getDocs
} from 'firebase/firestore';
// Simple query
async function getActiveUsers() {
const q = query(
collection(db, 'users'),
where('status', '==', 'active')
);
const querySnapshot = await getDocs(q);
return querySnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
}
// Complex query with multiple conditions
async function searchUsers(role, minAge) {
const q = query(
collection(db, 'users'),
where('role', '==', role),
where('age', '>=', minAge),
orderBy('age', 'asc'),
limit(10)
);
const querySnapshot = await getDocs(q);
return querySnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
}
// Query operators: ==, !=, <, <=, >, >=, array-contains,
// array-contains-any, in, not-in
Actualizaciones en tiempo real
Escucha cambios de datos en vivo:
import { doc, onSnapshot, collection } from 'firebase/firestore';
// Listen to a single document
function subscribeToUser(userId, callback) {
const unsubscribe = onSnapshot(doc(db, 'users', userId), (doc) => {
if (doc.exists()) {
callback({ id: doc.id, ...doc.data() });
}
});
// Return unsubscribe function to stop listening
return unsubscribe;
}
// Listen to a collection
function subscribeToUsers(callback) {
const q = query(collection(db, 'users'), where('status', '==', 'active'));
const unsubscribe = onSnapshot(q, (querySnapshot) => {
const users = [];
querySnapshot.forEach((doc) => {
users.push({ id: doc.id, ...doc.data() });
});
callback(users);
});
return unsubscribe;
}
// Usage in React
useEffect(() => {
const unsubscribe = subscribeToUsers((users) => {
setUsers(users);
});
return () => unsubscribe(); // Cleanup on unmount
}, []);
Actualizar documentos
Modifica documentos existentes:
import { doc, updateDoc, arrayUnion, arrayRemove, increment } from 'firebase/firestore';
// Simple update
async function updateUser(userId, data) {
const userRef = doc(db, 'users', userId);
await updateDoc(userRef, {
name: data.name,
updatedAt: new Date()
});
}
// Update specific fields
async function updateUserEmail(userId, newEmail) {
const userRef = doc(db, 'users', userId);
await updateDoc(userRef, {
email: newEmail
});
}
// Add to an array
async function addTag(userId, tag) {
const userRef = doc(db, 'users', userId);
await updateDoc(userRef, {
tags: arrayUnion(tag)
});
}
// Remove from an array
async function removeTag(userId, tag) {
const userRef = doc(db, 'users', userId);
await updateDoc(userRef, {
tags: arrayRemove(tag)
});
}
// Increment a number
async function incrementScore(userId, points) {
const userRef = doc(db, 'users', userId);
await updateDoc(userRef, {
score: increment(points)
});
}
Eliminar documentos
Elimina documentos y campos:
import { doc, deleteDoc, updateDoc, deleteField } from 'firebase/firestore';
// Delete a document
async function deleteUser(userId) {
await deleteDoc(doc(db, 'users', userId));
console.log('User deleted');
}
// Delete a specific field
async function removeUserBio(userId) {
const userRef = doc(db, 'users', userId);
await updateDoc(userRef, {
bio: deleteField()
});
}
Subcolecciones
Organiza datos con colecciones anidadas:
import { collection, doc, addDoc, getDocs } from 'firebase/firestore';
// Add a document to a subcollection
async function addPost(userId, postData) {
const postsRef = collection(db, 'users', userId, 'posts');
const docRef = await addDoc(postsRef, {
title: postData.title,
content: postData.content,
createdAt: new Date()
});
return docRef.id;
}
// Get all documents from a subcollection
async function getUserPosts(userId) {
const postsRef = collection(db, 'users', userId, 'posts');
const querySnapshot = await getDocs(postsRef);
return querySnapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
}));
}
// Reference a specific document in a subcollection
const postRef = doc(db, 'users', userId, 'posts', postId);
Escrituras en lote y transacciones
Ejecuta múltiples operaciones de forma atómica:
import { writeBatch, runTransaction, doc } from 'firebase/firestore';
// Batch writes - up to 500 operations
async function batchUpdate() {
const batch = writeBatch(db);
const user1Ref = doc(db, 'users', 'user1');
const user2Ref = doc(db, 'users', 'user2');
batch.update(user1Ref, { status: 'inactive' });
batch.update(user2Ref, { status: 'inactive' });
batch.delete(doc(db, 'users', 'user3'));
await batch.commit();
console.log('Batch completed');
}
// Transactions - for read-then-write operations
async function transferPoints(fromId, toId, points) {
await runTransaction(db, async (transaction) => {
const fromRef = doc(db, 'users', fromId);
const toRef = doc(db, 'users', toId);
const fromDoc = await transaction.get(fromRef);
const toDoc = await transaction.get(toRef);
if (!fromDoc.exists() || !toDoc.exists()) {
throw new Error('User not found');
}
const fromPoints = fromDoc.data().points;
if (fromPoints < points) {
throw new Error('Insufficient points');
}
transaction.update(fromRef, { points: fromPoints - points });
transaction.update(toRef, { points: toDoc.data().points + points });
});
}
Tipos de datos en Firestore
Firestore soporta varios tipos de datos:
import { Timestamp, GeoPoint, serverTimestamp } from 'firebase/firestore';
const document = {
// Primitive types
name: 'John Doe', // string
age: 30, // number
isActive: true, // boolean
nullable: null, // null
// Complex types
tags: ['developer', 'designer'], // array
address: { // map (nested object)
city: 'San Francisco',
country: 'USA'
},
// Special Firestore types
createdAt: serverTimestamp(), // Server timestamp
birthday: Timestamp.fromDate(new Date('1990-01-15')), // Timestamp
location: new GeoPoint(37.7749, -122.4194), // GeoPoint
// Reference to another document
teamRef: doc(db, 'teams', 'team123')
};
💡 Puntos clave
- • Firestore organiza los datos en colecciones y documentos
- • Usa addDoc para IDs autogenerados y setDoc para IDs personalizados
- • Los listeners en tiempo real con onSnapshot mantienen datos sincronizados
- • Consulta documentos con where, orderBy y limit
- • Usa transacciones para operaciones de lectura y escritura
- • Las subcolecciones ayudan a organizar datos jerárquicos
📚 Más recursos
-
Documentación de Cloud Firestore →
Guías oficiales y referencia de API de Firestore
-
Guía de consultas en Firestore →
Aprende técnicas avanzadas de consulta
-
Modelo de datos de Firestore →
Buenas prácticas para estructurar tus datos