¿Qué es Firebase Realtime Database?
Firebase Realtime Database es una base de datos NoSQL alojada en la nube que almacena datos como JSON y los sincroniza en tiempo real con cada cliente conectado. Cuando construyes apps multiplataforma, todos los clientes comparten una sola instancia de base de datos y reciben actualizaciones con los datos más recientes.
Aunque Cloud Firestore es la opción más nueva, Realtime Database sigue siendo excelente para estructuras simples, requerimientos de baja latencia y escenarios donde necesitas escrituras muy rápidas.
⚡ Realtime Database vs Cloud Firestore
Realtime Database
- • Datos almacenados como un solo árbol JSON
- • Menor latencia para operaciones simples
- • Se cobra por datos transferidos
- • Solo una región
- • Consultas más simples
Cloud Firestore
- • Datos en documentos y colecciones
- • Soporta consultas más complejas
- • Se cobra por operación
- • Multi-región disponible
- • Mejor escalado para apps complejas
Configurar Realtime Database
Inicializa Realtime Database en tu aplicación:
import { initializeApp } from 'firebase/app';
import { getDatabase } from 'firebase/database';
const firebaseConfig = {
// Your config here
databaseURL: 'https://your-project.firebaseio.com'
};
const app = initializeApp(firebaseConfig);
const database = getDatabase(app);
export { database };
Entender la estructura JSON
Realtime Database almacena datos como un único árbol JSON:
// Example database structure
{
"users": {
"user1": {
"name": "Alice",
"email": "alice@example.com",
"profile": {
"avatar": "https://...",
"bio": "Developer"
}
},
"user2": {
"name": "Bob",
"email": "bob@example.com"
}
},
"posts": {
"post1": {
"title": "Hello World",
"author": "user1",
"timestamp": 1634567890
}
},
"user-posts": {
"user1": {
"post1": true,
"post2": true
}
}
}
Escribir datos
Hay varias formas de escribir datos en Realtime Database:
Establecer datos (sobrescribe)
import { ref, set } from 'firebase/database';
// Write to a specific path (overwrites existing data)
async function writeUserData(userId, name, email) {
await set(ref(database, 'users/' + userId), {
name: name,
email: email,
createdAt: Date.now()
});
console.log('User data saved');
}
writeUserData('user123', 'John Doe', 'john@example.com');
Agregar datos (clave autogenerada)
import { ref, push, set } from 'firebase/database';
// Create a new child with auto-generated key
async function addPost(title, content, authorId) {
const postsRef = ref(database, 'posts');
const newPostRef = push(postsRef);
await set(newPostRef, {
title: title,
content: content,
author: authorId,
timestamp: Date.now()
});
console.log('New post ID:', newPostRef.key);
return newPostRef.key;
}
Actualizar datos (actualización parcial)
import { ref, update } from 'firebase/database';
// Update specific fields without overwriting others
async function updateUserProfile(userId, updates) {
const userRef = ref(database, 'users/' + userId);
await update(userRef, {
name: updates.name,
'profile/bio': updates.bio, // Update nested path
updatedAt: Date.now()
});
}
// Multi-path updates (atomic)
async function publishPost(postId, userId) {
const updates = {};
updates['/posts/' + postId + '/published'] = true;
updates['/user-posts/' + userId + '/' + postId] = true;
await update(ref(database), updates);
}
Leer datos
Lee datos una vez o escucha actualizaciones en tiempo real:
Leer una vez
import { ref, get, child } from 'firebase/database';
// Read data once
async function getUser(userId) {
const dbRef = ref(database);
const snapshot = await get(child(dbRef, 'users/' + userId));
if (snapshot.exists()) {
console.log('User data:', snapshot.val());
return snapshot.val();
} else {
console.log('No user found');
return null;
}
}
Escuchar actualizaciones en tiempo real
import { ref, onValue, off } from 'firebase/database';
// Subscribe to changes
function subscribeToUser(userId, callback) {
const userRef = ref(database, 'users/' + userId);
const unsubscribe = onValue(userRef, (snapshot) => {
if (snapshot.exists()) {
callback(snapshot.val());
} else {
callback(null);
}
});
// Return function to stop listening
return () => off(userRef);
}
// Usage
const unsubscribe = subscribeToUser('user123', (userData) => {
console.log('User updated:', userData);
});
// Later, stop listening
unsubscribe();
Escuchar eventos de hijos
Escucha cambios específicos en nodos hijos:
import { ref, onChildAdded, onChildChanged, onChildRemoved } from 'firebase/database';
const postsRef = ref(database, 'posts');
// New child added
onChildAdded(postsRef, (snapshot) => {
console.log('New post:', snapshot.key, snapshot.val());
});
// Child changed
onChildChanged(postsRef, (snapshot) => {
console.log('Post updated:', snapshot.key, snapshot.val());
});
// Child removed
onChildRemoved(postsRef, (snapshot) => {
console.log('Post deleted:', snapshot.key);
});
Consultar datos
Filtra y ordena datos con consultas:
import {
ref,
query,
orderByChild,
orderByKey,
orderByValue,
limitToFirst,
limitToLast,
startAt,
endAt,
equalTo,
get
} from 'firebase/database';
// Order by a child property
async function getRecentPosts(limit = 10) {
const postsRef = ref(database, 'posts');
const q = query(
postsRef,
orderByChild('timestamp'),
limitToLast(limit)
);
const snapshot = await get(q);
const posts = [];
snapshot.forEach((child) => {
posts.push({ id: child.key, ...child.val() });
});
return posts.reverse(); // Most recent first
}
// Filter by value
async function getPostsByAuthor(authorId) {
const postsRef = ref(database, 'posts');
const q = query(
postsRef,
orderByChild('author'),
equalTo(authorId)
);
const snapshot = await get(q);
const posts = [];
snapshot.forEach((child) => {
posts.push({ id: child.key, ...child.val() });
});
return posts;
}
// Range queries
async function getUsersInRange(startName, endName) {
const usersRef = ref(database, 'users');
const q = query(
usersRef,
orderByChild('name'),
startAt(startName),
endAt(endName)
);
const snapshot = await get(q);
return snapshot.val();
}
Eliminar datos
Elimina datos de la base de datos:
import { ref, remove, set } from 'firebase/database';
// Remove a node and all its children
async function deleteUser(userId) {
await remove(ref(database, 'users/' + userId));
console.log('User deleted');
}
// Alternative: set to null
async function deletePost(postId) {
await set(ref(database, 'posts/' + postId), null);
}
// Delete multiple paths atomically
async function deleteUserCompletely(userId) {
const updates = {};
updates['/users/' + userId] = null;
updates['/user-posts/' + userId] = null;
await update(ref(database), updates);
}
Transacciones
Maneja actualizaciones concurrentes de forma segura con transacciones:
import { ref, runTransaction } from 'firebase/database';
// Increment a counter safely
async function incrementLikes(postId) {
const likesRef = ref(database, 'posts/' + postId + '/likes');
await runTransaction(likesRef, (currentLikes) => {
// If null, initialize to 0
return (currentLikes || 0) + 1;
});
}
// More complex transaction
async function toggleLike(postId, userId) {
const postRef = ref(database, 'posts/' + postId);
const result = await runTransaction(postRef, (post) => {
if (post) {
if (!post.likedBy) {
post.likedBy = {};
}
if (post.likedBy[userId]) {
// Unlike
post.likes = (post.likes || 1) - 1;
delete post.likedBy[userId];
} else {
// Like
post.likes = (post.likes || 0) + 1;
post.likedBy[userId] = true;
}
}
return post;
});
return result.committed;
}
Capacidades offline
Habilita persistencia offline:
import { getDatabase, enablePersistence } from 'firebase/database';
// Enable offline persistence (web)
// Note: This is enabled by default on mobile SDKs
// For web, use keepSynced to ensure data is available offline
import { ref, onDisconnect, keepSynced } from 'firebase/database';
// Keep a path synced for offline access
const postsRef = ref(database, 'posts');
keepSynced(postsRef, true);
// Handle disconnection
const presenceRef = ref(database, 'users/' + userId + '/online');
set(presenceRef, true);
// Set value when user disconnects
onDisconnect(presenceRef).set(false);
// Or remove on disconnect
const connectionRef = ref(database, 'connections/' + odId
onDisconnect(connectionRef).remove();
Hook de React para Realtime Database
Crea un hook personalizado para React:
import { useState, useEffect } from 'react';
import { ref, onValue, off } from 'firebase/database';
import { database } from '../lib/firebase';
export function useRealtimeData(path) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const dataRef = ref(database, path);
const handleData = (snapshot) => {
setData(snapshot.val());
setLoading(false);
};
const handleError = (err) => {
setError(err);
setLoading(false);
};
onValue(dataRef, handleData, handleError);
return () => off(dataRef);
}, [path]);
return { data, loading, error };
}
// Usage
function UserProfile({ userId }) {
const { data: user, loading, error } = useRealtimeData(`users/${userId}`);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!user) return <div>User not found</div>;
return <div>Welcome, {user.name}!</div>;
}
💡 Puntos clave
- • Realtime Database almacena datos como un único árbol JSON
- • Usa set para sobrescribir, update para actualizaciones parciales, push para IDs automáticos
- • onValue proporciona sincronización en tiempo real
- • Las transacciones garantizan actualizaciones concurrentes seguras
- • Estructura los datos en plano para evitar lecturas muy anidadas
- • Usa índices en reglas para mejorar el rendimiento de consultas
📚 Más recursos
-
Documentación de Realtime Database →
Guías oficiales de Firebase Realtime Database
-
Estructura tu base de datos →
Buenas prácticas para organizar datos JSON
-
Capacidades offline →
Habilita persistencia de datos sin conexión