Skip to main content
RapidDev - Software Development Agency
firebase-tutorial

How to Read Data from Firebase Realtime Database

Read data from Firebase Realtime Database using the modular SDK v9+ with ref() to target a path and get() for one-time reads or onValue() for real-time listeners. Use orderByChild(), equalTo(), limitToFirst(), and limitToLast() to filter and sort results. One-time reads with get() are best for data that rarely changes, while onValue() provides instant updates whenever the data at the referenced path changes.

What you'll learn

  • How to read data once with ref() and get()
  • How to listen for real-time updates with onValue()
  • How to filter and sort data with orderByChild() and equalTo()
  • How to unsubscribe from listeners to prevent memory leaks
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner8 min read10-15 minFirebase JS SDK v9+, Realtime Database (Spark and Blaze plans)March 2026RapidDev Engineering Team
TL;DR

Read data from Firebase Realtime Database using the modular SDK v9+ with ref() to target a path and get() for one-time reads or onValue() for real-time listeners. Use orderByChild(), equalTo(), limitToFirst(), and limitToLast() to filter and sort results. One-time reads with get() are best for data that rarely changes, while onValue() provides instant updates whenever the data at the referenced path changes.

Reading Data from Firebase Realtime Database

Firebase Realtime Database stores data as a single JSON tree. You access specific parts of that tree using references, which are paths like 'users/uid123/profile'. This tutorial covers one-time data fetches with get(), real-time listeners with onValue(), ordering and filtering with query constraints, and properly cleaning up listeners. All examples use the modular SDK v9+ import style.

Prerequisites

  • A Firebase project with Realtime Database enabled
  • Firebase JS SDK v9+ installed (npm install firebase)
  • Data already written to your Realtime Database
  • Basic JavaScript or TypeScript knowledge

Step-by-step guide

1

Initialize Realtime Database and read data once

Import the required functions from the firebase/database module and initialize your database instance. Use ref() to create a reference to a specific path in your database, then call get() to fetch the data at that path once. The returned snapshot has a val() method that returns the JavaScript object stored at that location. Always check snapshot.exists() before calling val() to handle missing data.

typescript
1import { initializeApp } from 'firebase/app'
2import { getDatabase, ref, get, child } from 'firebase/database'
3
4const app = initializeApp({
5 apiKey: 'YOUR_API_KEY',
6 authDomain: 'YOUR_PROJECT.firebaseapp.com',
7 projectId: 'YOUR_PROJECT_ID',
8 databaseURL: 'https://YOUR_PROJECT.firebaseio.com'
9})
10
11const db = getDatabase(app)
12
13async function readUserProfile(userId: string) {
14 const snapshot = await get(ref(db, `users/${userId}/profile`))
15
16 if (snapshot.exists()) {
17 console.log('Profile:', snapshot.val())
18 return snapshot.val()
19 } else {
20 console.log('No profile found for this user.')
21 return null
22 }
23}

Expected result: The function returns the user profile object if it exists, or null with a console message if the path has no data.

2

Listen for real-time updates with onValue()

Use onValue() to subscribe to a database path and receive updates in real time. The callback fires immediately with the current data, then again every time the data at that path (or any child path) changes. Store the unsubscribe function returned by onValue() and call it when you no longer need updates. In the callback, always check snapshot.exists() because the path could be deleted.

typescript
1import { getDatabase, ref, onValue } from 'firebase/database'
2
3const db = getDatabase()
4
5function listenToMessages(chatId: string) {
6 const messagesRef = ref(db, `chats/${chatId}/messages`)
7
8 const unsubscribe = onValue(messagesRef, (snapshot) => {
9 if (snapshot.exists()) {
10 const messages = snapshot.val()
11 // messages is an object with push keys as properties
12 const messageList = Object.entries(messages).map(
13 ([key, value]: [string, any]) => ({
14 id: key,
15 ...value
16 })
17 )
18 console.log('Messages:', messageList)
19 } else {
20 console.log('No messages yet.')
21 }
22 })
23
24 // Return unsubscribe for cleanup
25 return unsubscribe
26}

Expected result: The callback fires immediately with all existing messages and then re-fires whenever a message is added, changed, or deleted.

3

Listen for specific child events

Instead of receiving the entire dataset on every change, use onChildAdded(), onChildChanged(), and onChildRemoved() to listen for specific types of changes. onChildAdded fires once for each existing child and then for each new child. onChildChanged fires when any child's data changes. onChildRemoved fires when a child is deleted. This is more efficient than onValue for large lists.

typescript
1import { getDatabase, ref, onChildAdded, onChildChanged, onChildRemoved } from 'firebase/database'
2
3const db = getDatabase()
4const messagesRef = ref(db, 'chats/room1/messages')
5
6const unsubAdded = onChildAdded(messagesRef, (snapshot) => {
7 console.log('New message:', snapshot.key, snapshot.val())
8})
9
10const unsubChanged = onChildChanged(messagesRef, (snapshot) => {
11 console.log('Updated message:', snapshot.key, snapshot.val())
12})
13
14const unsubRemoved = onChildRemoved(messagesRef, (snapshot) => {
15 console.log('Deleted message:', snapshot.key, snapshot.val())
16})
17
18// Cleanup all listeners
19// unsubAdded(); unsubChanged(); unsubRemoved();

Expected result: Each listener fires only for its specific event type, enabling efficient incremental UI updates.

4

Filter and sort data with query constraints

Wrap a ref in query() with ordering and filtering constraints. Use orderByChild() to sort by a child key, then equalTo() to filter to a specific value, or limitToFirst()/limitToLast() to cap results. Realtime Database supports one orderBy per query. The snapshot.forEach() method iterates children in the sorted order.

typescript
1import { getDatabase, ref, query, orderByChild, equalTo, limitToFirst, get } from 'firebase/database'
2
3const db = getDatabase()
4
5// Get all users in the 'admin' role
6async function getAdminUsers() {
7 const usersQuery = query(
8 ref(db, 'users'),
9 orderByChild('role'),
10 equalTo('admin')
11 )
12
13 const snapshot = await get(usersQuery)
14 const admins: any[] = []
15 snapshot.forEach((child) => {
16 admins.push({ id: child.key, ...child.val() })
17 })
18 return admins
19}
20
21// Get the 10 most recent posts
22async function getRecentPosts() {
23 const postsQuery = query(
24 ref(db, 'posts'),
25 orderByChild('timestamp'),
26 limitToLast(10)
27 )
28
29 const snapshot = await get(postsQuery)
30 const posts: any[] = []
31 snapshot.forEach((child) => {
32 posts.push({ id: child.key, ...child.val() })
33 })
34 return posts.reverse() // limitToLast returns ascending, reverse for newest first
35}

Expected result: getAdminUsers returns only users with role 'admin'. getRecentPosts returns the 10 newest posts sorted by timestamp.

5

Handle errors and offline behavior

Pass an error callback as the third argument to onValue() to handle permission errors or connectivity issues. Realtime Database has built-in offline support — it caches data locally and serves reads from cache when offline. Use the .info/connected path to monitor connection state and show online/offline status in your UI.

typescript
1import { getDatabase, ref, onValue } from 'firebase/database'
2
3const db = getDatabase()
4
5// Listen with error handling
6onValue(
7 ref(db, 'protectedData'),
8 (snapshot) => {
9 console.log('Data:', snapshot.val())
10 },
11 (error) => {
12 console.error('Permission denied or network error:', error.message)
13 }
14)
15
16// Monitor connection state
17onValue(ref(db, '.info/connected'), (snapshot) => {
18 const isConnected = snapshot.val() === true
19 console.log(isConnected ? 'Connected' : 'Disconnected')
20})

Expected result: The error callback logs permission or network errors. The connection monitor logs the current connectivity state.

Complete working example

realtime-db-reader.ts
1import { initializeApp } from 'firebase/app'
2import {
3 getDatabase,
4 ref,
5 get,
6 query,
7 orderByChild,
8 equalTo,
9 limitToFirst,
10 limitToLast,
11 onValue,
12 onChildAdded,
13 onChildRemoved,
14 Unsubscribe
15} from 'firebase/database'
16
17const app = initializeApp({
18 apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY!,
19 authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN!,
20 projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID!,
21 databaseURL: process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL!
22})
23
24const db = getDatabase(app)
25
26// One-time read
27export async function readOnce<T>(path: string): Promise<T | null> {
28 const snapshot = await get(ref(db, path))
29 return snapshot.exists() ? (snapshot.val() as T) : null
30}
31
32// Filtered query
33export async function queryByChild<T>(
34 path: string,
35 childKey: string,
36 value: string | number | boolean,
37 maxResults?: number
38): Promise<T[]> {
39 let q = query(ref(db, path), orderByChild(childKey), equalTo(value))
40 if (maxResults) {
41 q = query(ref(db, path), orderByChild(childKey), equalTo(value), limitToFirst(maxResults))
42 }
43
44 const snapshot = await get(q)
45 const results: T[] = []
46 snapshot.forEach((child) => {
47 results.push({ id: child.key, ...child.val() } as T)
48 })
49 return results
50}
51
52// Real-time listener
53export function subscribe(
54 path: string,
55 callback: (data: any) => void
56): Unsubscribe {
57 return onValue(
58 ref(db, path),
59 (snapshot) => callback(snapshot.exists() ? snapshot.val() : null),
60 (error) => console.error(`Listener error at ${path}:`, error.message)
61 )
62}
63
64// Connection state monitor
65export function onConnectionChange(
66 callback: (connected: boolean) => void
67): Unsubscribe {
68 return onValue(ref(db, '.info/connected'), (snapshot) => {
69 callback(snapshot.val() === true)
70 })
71}

Common mistakes when reading Data from Firebase Realtime Database

Why it's a problem: Forgetting to include databaseURL in the Firebase config, causing 'Cannot read properties of undefined' errors

How to avoid: Add databaseURL: 'https://YOUR_PROJECT.firebaseio.com' to your Firebase config object. Find the exact URL in Firebase Console under Project Settings.

Why it's a problem: Not calling the unsubscribe function from onValue(), causing memory leaks and duplicate listeners

How to avoid: Store the return value of onValue() and call it when the listener is no longer needed. In React, return it from useEffect's cleanup function.

Why it's a problem: Using orderByChild() without an .indexOn rule, causing the entire dataset to be downloaded and sorted client-side

How to avoid: Add an .indexOn rule in your database rules: { "users": { ".indexOn": ["role", "email"] } }. This lets the server filter and sort before sending data.

Best practices

  • Use get() for data you read once and onValue() only when you need real-time updates to minimize bandwidth usage
  • Always check snapshot.exists() before calling snapshot.val() to handle deleted or missing data
  • Add .indexOn rules for every field you query with orderByChild() to avoid full dataset downloads
  • Use onChildAdded/Changed/Removed instead of onValue for large lists to receive incremental updates
  • Keep your database structure shallow — deeply nested data forces large downloads when you only need part of the tree
  • Monitor .info/connected to show users their online/offline status and queue writes when disconnected
  • Use limitToFirst() or limitToLast() to cap the number of results and reduce bandwidth costs

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I have a Firebase Realtime Database with a 'users' node containing user profiles and a 'messages' node with chat messages. Show me how to read data using the Firebase modular SDK v9+, including one-time reads with get(), real-time listeners with onValue(), filtering with orderByChild and equalTo, and proper cleanup of listeners.

Firebase Prompt

Create a TypeScript utility module for reading Firebase Realtime Database data using modular SDK v9+. Include type-safe one-time reads, real-time subscriptions with error handling, filtered queries using orderByChild/equalTo/limitToLast, and connection state monitoring.

Frequently asked questions

What is the difference between Realtime Database and Firestore?

Realtime Database stores data as a single JSON tree with bandwidth-based pricing and excels at low-latency simple updates like presence and typing indicators. Firestore uses a document-collection model with operation-based pricing and supports more complex queries, offline web persistence, and better scaling beyond 200,000 concurrent connections.

Does Realtime Database work offline?

Yes. Realtime Database automatically caches data locally and serves reads from the cache when the device is offline. When connectivity is restored, it syncs pending writes and updates the local cache with server changes.

How many simultaneous listeners can I have?

There is no hard limit on the number of listeners, but each listener maintains an open connection. The Spark free plan allows 100 simultaneous connections per database. Blaze allows up to 200,000. Each browser tab counts as one connection regardless of how many listeners are active.

Can I query by multiple fields in Realtime Database?

Realtime Database supports only one orderByChild per query. For multi-field queries, create a composite key field (like 'category_price') that combines the values, or use Firestore which natively supports compound queries.

How do I paginate results in Realtime Database?

Use limitToFirst() or limitToLast() with startAt() or endAt() to implement cursor-based pagination. After fetching a page, use the last item's sort value as the starting point for the next query.

Can RapidDev help me implement real-time data reading in my Firebase app?

Yes. RapidDev can set up efficient Realtime Database read patterns including real-time listeners, optimized queries with proper indexing rules, and offline-capable data synchronization tailored to your application.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.