TechLead
Lección 4 de 8
7 min de lectura
Firebase

Realtime Database

Crea aplicaciones en tiempo real con la base de datos original de Firebase

¿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

Continuar Aprendiendo