Firebase Database Example for React Native Apps

A complete guide with a Firebase database example for React Native (Expo). Learn to set up Firestore & Realtime DB, CRUD, auth, and security rules.

SA

By Suraj Ahmed

7th Apr 2026

Firebase Database Example for React Native Apps

A product team usually reaches the same moment at the same time. The designer has mocked a live chat. The PM wants users to see updates instantly. The developer knows “instant” is where backend work gets expensive.

That is where a good firebase database example becomes more than a code sample. It becomes an architecture decision with product consequences. Pick the wrong model and the prototype feels fine, then falls apart when the team adds permissions, pagination, or multi-tenant accounts. Pick the right one and the app feels fast, the scope stays sane, and engineering handoff is clean.

Firebase is attractive because it removes a lot of backend setup that slows early mobile work. You can authenticate users, store app data, sync changes in real time, and ship a usable prototype without standing up your own API first. For React Native teams, that matters because the mobile UI is already enough work.

The harder question is not whether Firebase can power the feature. It can. The harder question is which Firebase database fits the product you are building. A team building a lightweight presence indicator has different needs than a team building a marketplace, admin dashboard, or structured content app.

Building Your First Real-Time Mobile App

The first real-time feature often starts small.

A founder says, “Let’s add comments that appear instantly.” A PM adds collaborative voting to a sprint board. A designer wants a shared checklist that updates for everyone in the room. None of these sound like “build distributed state synchronization,” but that is what the team is signing up for.

Where teams get stuck

The front end is rarely the blocker. React Native makes it straightforward to render a chat list, a poll, or a live activity feed.

The trouble starts behind the screen:

  • State consistency: Two users tap at once and expect to see the same result.
  • Offline behavior: Mobile users lose signal, reopen the app, and expect their changes not to vanish.
  • Security: Test data is easy. User-owned data is not.
  • Data shape: A quick prototype can become painful if the data model fights the product later.

When teams skip these questions, they often build a UI that looks finished but behaves like a demo. It works on one device, with one tester, on strong Wi-Fi. That is not enough for product validation.

Why Firebase usually enters the conversation

Firebase reduces the amount of infrastructure a product team has to invent before users can touch the feature.

For a mobile product, that changes the pace of decision-making. The PM can test whether live updates improve engagement. The designer can check whether a collaborative flow feels intuitive. The junior developer can build against a real backend instead of waiting for custom endpoints.

Practical rule: If the feature’s core value depends on shared state changing in front of the user, model the data flow first and the screen second.

That does not mean Firebase makes architecture irrelevant. It makes architecture visible sooner.

A useful way to think about it

For a first release, teams often decide between two different types of work:

QuestionProduct implication
Do we need simple, ultra-fast sync?Better fit for low-friction live interactions
Do we need richer query patterns later?Better fit for feeds, filters, and complex views
Is the team validating an MVP or designing a long-lived platform?Changes how much future flexibility matters
Will users collaborate in the same session?Real-time listeners become central, not optional

That is why a firebase database example should never stop at “how to write data.” The write operation is easy. The long-term cost sits in how the data is shaped, queried, and secured.

Choosing Your Firebase Database Realtime vs Firestore

Teams often ask this as a developer question. It is really a product question.

If your app needs shared cursors, presence, live polls, or rapid-fire state changes, Realtime Database often feels natural. Firebase describes it as a cloud-hosted JSON tree with updates delivered in milliseconds, and notes that those updates can be up to 100 times faster than traditional HTTP polling methods for collaborative apps in its Realtime Database documentation.

If your app needs more structured records, cleaner entity boundaries, and query flexibility, Firestore is usually easier to grow with. The trade-off is not “old versus new.” It is streaming simplicity versus structured querying.

Infographic

How the choice affects product behavior

A product team should evaluate the database the same way it evaluates onboarding or navigation. Ask what the user is doing repeatedly.

If users are continuously updating the same small piece of data, Realtime Database is strong. Presence indicators, cursor positions, vote counts, and lightweight chat streams fit that pattern well.

If users need to browse, filter, search, and combine different record types, Firestore usually keeps the app easier to reason about. It maps more cleanly to product concepts like users, projects, tasks, and comments.

Side by side product decision table

Decision areaRealtime DatabaseFirestore
Best fitLive synchronization of simple, high-frequency stateStructured app data with more complex views
Mental modelOne JSON treeCollections and documents
Team riskEasy to start, often cleaner long-term boundariesSlightly more setup, often cleaner long-term boundaries
Good product examplesChat, presence, live polls, collaborative cursorsTask apps, content apps, admin-heavy products
Common failure modeDeep data trees that become hard to queryOverengineering a simple real-time feature

What works well with Realtime Database

A lot of MVPs benefit from the raw directness of the JSON tree. You can subscribe to a location and update the UI immediately.

That makes Realtime Database good when the product value is obvious only if the user sees updates instantly. A live poll is the classic case. A voting screen that requires refreshes feels broken, even if the backend is correct.

It also helps when the team needs to move quickly. A PM can understand the shape of the data. A designer can map screens to nodes. A junior developer can inspect values without understanding a large backend stack.

What does not work well with Realtime Database

The danger is overconfidence early.

Because the model is flexible, teams keep nesting. A user contains chats. Chats contain messages. Messages contain reactions. Then someone needs pagination, moderation, analytics, or organization-level access control. Now every read drags along more data than the screen needs.

That is why teams comparing Firebase to alternatives should also understand the broader backend trade-offs. This overview of Supabase for product teams is useful if your team is weighing structured relational workflows against Firebase’s real-time-first approach.

Key takeaway: Choose Realtime Database when the primary user promise is “everyone sees the change now.” Choose Firestore when the primary user promise is “users can organize, retrieve, and manage data cleanly as the app grows.”

A simple decision filter

Use Realtime Database if most of these are true:

  • The feature is live-first: Chat, status, collaborative actions.
  • The payload is small: Short updates, flags, counters, messages.
  • The screen is session-driven: Users care about what is happening now.

Use Firestore if most of these are true:

  • The feature is record-driven: Projects, documents, orders, profiles.
  • The UI needs multiple filtered views: By owner, status, category, or workflow stage.
  • The roadmap includes admin tooling: Structured data helps fast.

For many teams, the smartest move is mixed usage over time. But for a first release, forcing one clear choice prevents a lot of accidental complexity.

Your React Native and Firebase Project Setup

Once the database decision is made, the setup should stay boring. Boring is good here.

The goal is a React Native app that connects cleanly to Firebase, keeps config isolated, and gives the team one reliable place to initialize services. That matters because misconfigured setup creates fake bugs. The UI looks broken when the underlying issue is an environment mismatch.

A modern workspace featuring a laptop displaying code and a tablet open to the Firebase console.

Start with the app shell

For a React Native team using Expo, create the app first and keep Firebase setup in its own file. If someone on the team is still getting oriented, this primer on React Native fundamentals gives useful context before wiring services.

Install the Firebase SDK in your project:

  1. Create the app: Start a new Expo project with your preferred template.
  2. Add Firebase: Install the Firebase JavaScript SDK.
  3. Separate config: Put Firebase initialization in firebase.ts or firebase.js.
  4. Export instances: Export auth, database, or firestore from one place.

A simple setup looks like this:

// firebase.js
import { initializeApp } from 'firebase/app'
import { getAuth } from 'firebase/auth'
import { getDatabase } from 'firebase/database'
import { getFirestore } from 'firebase/firestore'

const firebaseConfig = {
  apiKey: 'YOUR_API_KEY',
  authDomain: 'YOUR_AUTH_DOMAIN',
  databaseURL: 'YOUR_DATABASE_URL',
  projectId: 'YOUR_PROJECT_ID',
  storageBucket: 'YOUR_STORAGE_BUCKET',
  messagingSenderId: 'YOUR_SENDER_ID',
  appId: 'YOUR_APP_ID',
}

const app = initializeApp(firebaseConfig)

export const auth = getAuth(app)
export const realtimeDb = getDatabase(app)
export const firestoreDb = getFirestore(app)

Why this structure matters

A lot of early app projects scatter Firebase imports across screens. That works for a day. Then the team adds auth state, environment switching, and tests.

A single config module gives you three advantages:

Setup choiceWhy it helps
One initialization filePrevents duplicate app instances
Named exports for servicesKeeps import paths consistent
Environment-aware config laterMakes staging and production less painful

Register the app in Firebase Console

In the Firebase console, create a project and register the mobile app. The exact screens vary, but the workflow is consistent:

  • Create the Firebase project
  • Register your app identifier
  • Enable Authentication
  • Enable Realtime Database or Firestore
  • Choose test settings carefully

Do not leave test-mode permissions in place longer than needed. Teams often do this “just for setup,” then forget.

Tip: Treat configuration as product infrastructure, not developer ceremony. If the PM or designer cannot launch the same build and hit the same backend, feedback slows down immediately.

Add a thin service layer early

Even for a prototype, avoid calling Firebase directly from every component. A thin service layer keeps UI concerns separate from data concerns.

For example:

// services/polls.js
import { ref, set, get } from 'firebase/database'
import { realtimeDb } from '../firebase'

export async function saveVote(pollId, userId, optionId) {
  await set(ref(realtimeDb, `votes/${pollId}/${userId}`), { optionId })
}

export async function getVotes(pollId) {
  const snapshot = await get(ref(realtimeDb, `votes/${pollId}`))
  return snapshot.val() || {}
}

This is not overengineering. It gives the team one place to change behavior when the data model evolves.

Common setup mistakes

  • Using Firebase directly inside screen files: Fast at first, messy by sprint two.
  • Skipping auth setup until later: Security rules become harder to apply retroactively.
  • Mixing Realtime Database and Firestore imports casually: Confuses the team if the architecture is not explicit.

A stable setup does not impress anyone in a demo. It saves the project when the first real bug appears.

A Practical Firebase Database Example CRUD and Listeners

A live poll is one of the best ways to understand Firebase in a React Native app.

The user model is simple. Each person picks one option. The result updates on every connected device without refresh. That makes it a practical firebase database example because it shows both the data write and the part users notice, which is the live UI change.

A person holding a smartphone displaying a live poll about favorite food choices on an app interface.

Poll data model choices

For a simple poll, both databases can work.

Realtime Database might store votes like this:

{
  "polls": {
    "poll_1": {
      "question": "Best lunch option?",
      "options": {
        "pizza": true,
        "sushi": true,
        "salad": true
      }
    }
  },
  "votes": {
    "poll_1": {
      "user_1": { "optionId": "pizza" },
      "user_2": { "optionId": "sushi" }
    }
  }
}

Firestore might model the same poll as one document plus a subcollection or separate votes collection.

For a team discussion, the product question is simple: do you need the easiest live sync, or do you expect richer filtering and reporting later?

Create a vote

In Firestore:

import { doc, setDoc } from 'firebase/firestore'
import { firestoreDb } from './firebase'

export async function castVoteFirestore(pollId, userId, optionId) {
  await setDoc(doc(firestoreDb, 'polls', pollId, 'votes', userId), {
    optionId,
    userId,
  })
}

In Realtime Database:

import { ref, set } from 'firebase/database'
import { realtimeDb } from './firebase'

export async function castVoteRealtime(pollId, userId, optionId) {
  await set(ref(realtimeDb, `votes/${pollId}/${userId}`), {
    optionId,
    userId,
  })
}

The user experience outcome is the same. A tap should feel immediate.

The implementation trade-off is different. Firestore reads more like records. Realtime Database reads more like shared state.

Read poll results

In Firestore, a one-time read could look like this:

import { collection, getDocs } from 'firebase/firestore'
import { firestoreDb } from './firebase'

export async function fetchVotesFirestore(pollId) {
  const snapshot = await getDocs(collection(firestoreDb, 'polls', pollId, 'votes'))
  return snapshot.docs.map((doc) => doc.data())
}

In Realtime Database:

import { ref, get } from 'firebase/database'
import { realtimeDb } from './firebase'

export async function fetchVotesRealtime(pollId) {
  const snapshot = await get(ref(realtimeDb, `votes/${pollId}`))
  return snapshot.val() || {}
}

Real-time listeners are the feature

A poll without listeners is just a form.

For Firestore, subscribe with onSnapshot:

import { collection, onSnapshot } from 'firebase/firestore'
import { firestoreDb } from './firebase'

export function subscribeToVotesFirestore(pollId, callback) {
  return onSnapshot(collection(firestoreDb, 'polls', pollId, 'votes'), (snapshot) => {
    const votes = snapshot.docs.map((doc) => doc.data())
    callback(votes)
  })
}

For Realtime Database, subscribe with onValue:

import { ref, onValue } from 'firebase/database'
import { realtimeDb } from './firebase'

export function subscribeToVotesRealtime(pollId, callback) {
  const votesRef = ref(realtimeDb, `votes/${pollId}`)
  return onValue(votesRef, (snapshot) => {
    callback(snapshot.val() || {})
  })
}

This is the moment Firebase earns its keep. The UI is no longer polling or manually refreshing. It is reacting to shared state.

Firebase’s REST documentation also highlights a core pattern for list-like writes: push() creates unique timestamp-based keys, and this pattern syncs to thousands of clients in under 100ms through a persistent connection in the official retrieve data docs. For a poll, user IDs often make better keys. For a feed or chat stream, push() is usually the right move.

Update an existing vote

Users change their minds. Your data model should allow that without hacks.

Firestore:

import { doc, updateDoc } from 'firebase/firestore'
import { firestoreDb } from './firebase'

export async function updateVoteFirestore(pollId, userId, optionId) {
  await updateDoc(doc(firestoreDb, 'polls', pollId, 'votes', userId), {
    optionId,
  })
}

Realtime Database:

import { ref, update } from 'firebase/database'
import { realtimeDb } from './firebase'

export async function updateVoteRealtime(pollId, userId, optionId) {
  await update(ref(realtimeDb, `votes/${pollId}/${userId}`), {
    optionId,
  })
}

Delete a vote

Retractions matter when the product needs flexibility. Maybe the poll closes later. Maybe users can clear their choice.

Firestore:

import { doc, deleteDoc } from 'firebase/firestore'
import { firestoreDb } from './firebase'

export async function deleteVoteFirestore(pollId, userId) {
  await deleteDoc(doc(firestoreDb, 'polls', pollId, 'votes', userId))
}

Realtime Database:

import { ref, remove } from 'firebase/database'
import { realtimeDb } from './firebase'

export async function deleteVoteRealtime(pollId, userId) {
  await remove(ref(realtimeDb, `votes/${pollId}/${userId}`))
}

Render results in React Native

A simple component can subscribe and recompute totals on each update:

import React, { useEffect, useState } from 'react'
import { View, Text } from 'react-native'
import { subscribeToVotesRealtime } from './pollService'

export default function PollResults({ pollId }) {
  const [totals, setTotals] = useState({})

  useEffect(() => {
    const unsubscribe = subscribeToVotesRealtime(pollId, (votes) => {
      const counts = {}

      Object.values(votes).forEach((vote) => {
        const optionId = vote.optionId
        counts[optionId] = (counts[optionId] || 0) + 1
      })

      setTotals(counts)
    })

    return () => unsubscribe()
  }, [pollId])

  return (
    <View>
      {Object.entries(totals).map(([option, count]) => (
        <Text key={option}>{option}: {count}</Text>
      ))}
    </View>
  )
}

The result is small, but product teams learn a lot from it. They can see whether users understand the interaction, whether immediate updates increase confidence, and whether the current data model is easy to extend.

A quick visual walkthrough helps if the team wants to see this flow in action before coding more screens:

What usually works and what usually fails

What works:

  • One clear owner key per vote
  • Real-time listener cleanup on unmount
  • Computed UI state from raw votes
  • Separate poll metadata from vote records

What fails:

  • Storing display-only totals as the source of truth
  • Putting all poll state in one giant nested object
  • Skipping unsubscribe logic
  • Letting anonymous writes through during testing

Practical advice: Build one complete live poll before building “a flexible survey platform.” Teams learn more from one real listener and one secure write path than from ten abstract models on a whiteboard.

Securing Your App with Auth and Database Rules

A real-time feature without security is a public editing tool.

Teams often slow down at this stage. The feature already works. The temptation is to leave broad read and write access in place while the team focuses on polish. That is fine for a local proof of concept and risky for anything beyond it.

A 3D render of a black padlock sitting amidst flowing golden and green digital cables with water droplets.

Start with identity, not rules syntax

Rules become much simpler once every action has a user attached to it.

For a React Native app, email and password auth is enough for a first secure flow:

import { createUserWithEmailAndPassword, signInWithEmailAndPassword } from 'firebase/auth'
import { auth } from './firebase'

export async function signUp(email, password) {
  return createUserWithEmailAndPassword(auth, email, password)
}

export async function signIn(email, password) {
  return signInWithEmailAndPassword(auth, email, password)
}

The PM gets testable user sessions. The designer can see signed-in states. The developer gets a stable uid for data ownership.

Apply ownership rules to the poll app

If each user can only create or change their own vote, your rules should reflect that directly.

A Realtime Database pattern looks like this:

{
  "rules": {
    "votes": {
      "$pollId": {
        "$userId": {
          ".read": "auth != null",
          ".write": "auth != null && auth.uid === $userId"
        }
      }
    }
  }
}

This rule set is straightforward. That is the point.

A first production rule set should answer plain product questions:

  • Who can read this data?
  • Who can write this data?
  • Can one user modify another user’s record?
  • Does the incoming data have the shape we expect?

Structure affects security and performance

Security is not separate from data modeling. A messy tree is harder to protect cleanly.

Firebase’s Realtime Database guidance warns that while nesting can go up to 32 levels, reading at any node pulls the entire subtree, so deep structures create bandwidth and latency problems. Firebase recommends keeping data as flat as possible, and its guidance uses chat data as the classic case where messages should sit in a separate top-level collection rather than deep inside a conversation object in the official explanation of nested data trade-offs.

That matters for rules too. Flat structures make it easier to express ownership.

Validate data, not just identity

Teams often stop at “user is signed in.” That protects less than they think.

A better baseline is to validate the fields being written. For a vote, you might require optionId to exist:

{
  "rules": {
    "votes": {
      "$pollId": {
        "$userId": {
          ".read": "auth != null",
          ".write": "auth != null && auth.uid === $userId",
          ".validate": "newData.hasChildren(['optionId'])"
        }
      }
    }
  }
}

That prevents accidental malformed writes from buggy clients.

Tip: Good rules are product logic made explicit. If the PM says “users can only vote once, and only for existing options,” the rules should help enforce that expectation.

A practical review checklist

Before shipping, review security with the whole team, not only engineering.

CheckWhy it matters
Authenticated reads only where neededPrevents casual data exposure
User-scoped writesBlocks cross-account edits
Minimal writable fieldsReduces damage from buggy clients
Flat data pathsEasier to secure and query

Common mistakes in early Firebase apps

  • Leaving test rules enabled
  • Using one shared path for multiple record types
  • Nesting user-owned content excessively
  • Assuming UI restrictions equal backend security

The strongest early-stage move is simple: authenticate first, store user-owned data in predictable paths, and write rules that match the product behavior exactly.

Scaling Your App and Accelerating Prototyping

The first version of a real-time app usually works before it scales.

That is normal. The problem is that some scaling issues are cheap to fix early and expensive later. Data structure is one of them. Multi-tenant boundaries are another. Teams feel this most when a promising prototype becomes the basis for production.

The scaling decisions that matter early

For Firebase Realtime Database, scaling is not only about traffic. It is about how the app partitions data and reads only what it needs.

The biggest early wins usually come from:

  • Denormalized paths: Put related data where the UI can fetch it cheaply.
  • Shallow structures: Avoid reads that drag in large sibling data.
  • Feature-specific listeners: Subscribe narrowly, not broadly.
  • Tenant boundaries: Decide where organizational separation belongs.

These choices do not make the prototype slower to build. They keep the prototype from teaching the team the wrong habits.

When sharding becomes the architectural question

For larger multi-tenant products, Realtime Database scales primarily through database sharding. On the Blaze plan, Firebase allows up to 1,000 separate database instances within one project, which makes it possible to map one organization to one database instance and distribute load. Firebase’s sharding guidance also makes the practical point that retrofitting this later is difficult, so the architecture should be planned early in the official sharding documentation.

A founder does not need to memorize that implementation detail. But the team should understand the product implication.

If the roadmap includes agency clients, workspaces, schools, or franchises, the app may need tenant-aware architecture from day one. That is not a premature optimization. It is a boundary decision.

Why prototyping should include data decisions

A lot of teams prototype only the interface. Then engineering rebuilds the feature properly later.

That split creates waste. The team validates the wrong thing. The screen may test well while the underlying data flow is unrealistic.

A better process is to prototype the app with real navigation, real auth states, and a real backend shape. That is especially useful when a team is evaluating whether to stay with Firebase or later migrate database architecture to the cloud in a more structured way.

Key takeaway: A prototype should de-risk the product and the data model at the same time. If it only validates the UI, half the risk is still hidden.

What a strong handoff looks like

The best product teams do three things before custom backend work expands:

  1. They prove the user flow with a working app, not just mockups.
  2. They pressure-test the data model with one real feature such as polling, presence, or chat.
  3. They document what must stay flexible, especially tenant boundaries, permissions, and query patterns.

That is where RapidNative is useful. It lets a team turn prompts, sketches, or a PRD into a shareable React Native app quickly, then export clean code when the concept is ready for engineering ownership. The benefit is not just speed. It is alignment. Founders, PMs, designers, and developers can evaluate the same running product before technical debt hardens.

If you are deciding between Realtime Database and Firestore, do not treat the choice as a backend detail. It changes how the app feels, how the roadmap expands, and how painful the handoff becomes later.


If your team wants to validate a real-time mobile feature without waiting on a full custom build, RapidNative is a practical place to start. You can prototype the React Native app, test the user flow with a live backend, and export production-ready code when the architecture is clear.

Ready to Build Your App?

Turn your idea into a production-ready React Native app in minutes.

Try It Now