TechLead
Lesson 4 of 8
7 min read
Firebase

Realtime Database

Build real-time applications with Firebase's original database solution

What is Firebase Realtime Database?

The Firebase Realtime Database is a cloud-hosted NoSQL database that stores data as JSON and synchronizes it in real-time to every connected client. When you build cross-platform apps, all clients share one database instance and automatically receive updates with the newest data.

While Cloud Firestore is the newer database option, Realtime Database is still excellent for simpler data structures, lower latency requirements, and scenarios where you need very fast writes.

⚡ Realtime Database vs Cloud Firestore

Realtime Database

  • • Data stored as one large JSON tree
  • • Lower latency for simple operations
  • • Charged per data transferred
  • • Single region only
  • • Simpler querying

Cloud Firestore

  • • Data stored in documents and collections
  • • More complex queries supported
  • • Charged per operation
  • • Multi-region available
  • • Better scaling for complex apps

Setting Up Realtime Database

Initialize the Realtime Database in your application:

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

Understanding the JSON Structure

Realtime Database stores data as a single JSON tree:

// 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
    }
  }
}

Writing Data

There are several ways to write data to the Realtime Database:

Set Data (Overwrites)

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

Push Data (Auto-generated Key)

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

Update Data (Partial Update)

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

Reading Data

Read data once or listen for real-time updates:

Read Once

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

Listen for Real-time Updates

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

Listening for Child Events

Listen for specific changes to child nodes:

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

Querying Data

Filter and sort data with queries:

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

Deleting Data

Remove data from the database:

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

Transactions

Handle concurrent updates safely with transactions:

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

Offline Capabilities

Enable offline persistence:

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

React Hook for Realtime Database

Create a custom hook for 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>;
}

💡 Key Takeaways

  • • Realtime Database stores data as a single JSON tree
  • • Use set for overwriting, update for partial updates, push for auto-IDs
  • • onValue provides real-time synchronization
  • • Transactions ensure safe concurrent updates
  • • Structure data flat to avoid deeply nested reads
  • • Use indexes in rules for query performance

📚 Learn More

Continue Learning