React + Node.js + Firebase

The full-stack pick for real-time apps - chat, live feeds, collaboration.

Real-time features are the hardest thing to bolt onto a traditional stack: you need persistent connections, a way to fan out updates, and reconnection handling. This stack sidesteps all of it by letting Firestore push changes straight to a React interface.


Who gets this pick

Every full-stack quiz run where you choose a real-time app (chat, live feed) ends here - regardless of experience, goal, hosting or team size. Real-time sync is the requirement that most narrows the field, so it decides the answer.


How the pieces fit together

This isn't a classic three-tier stack where every request goes through your server:

  • React renders the interface and re-renders whenever data changes.
  • Firestore is called directly from the browser. Components subscribe to queries and receive updates live; security rules decide who can read and write what - they are effectively your backend.
  • Node.js is optional, for the jobs a browser must never do: handling payment webhooks, sending email, calling APIs with secret keys, or using the Firebase Admin SDK. That code can run as Firebase Cloud Functions or on any Node host.

That's why this stack works even on shared hosting: the built React app is static files, and the real-time part runs on Google's servers.


Subscribing to live data in React

import { useEffect, useState } from "react";
import { collection, query, orderBy, limitToLast, onSnapshot } from "firebase/firestore";
import { db } from "./firebase";

export function useMessages() {
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    const q = query(collection(db, "messages"), orderBy("createdAt"), limitToLast(50));
    const unsubscribe = onSnapshot(q, (snap) => {
      setMessages(snap.docs.map((d) => ({ id: d.id, ...d.data() })));
    });
    return unsubscribe; // stop listening when the component unmounts
  }, []);

  return messages;
}

Any component that calls useMessages() now updates live whenever anyone sends a message.


A first project

  1. Build the plain-JavaScript version first with our real-time chat guide - it covers setup and security rules without React in the way.
  2. Port it to React with the hook above, adding a message form component.
  3. Add Firebase Authentication and show who sent each message.
  4. Add one Node.js task, such as a Cloud Function that removes messages older than 30 days.

Hosting and cost

The React app can live on Firebase Hosting, Netlify, Cloudflare Pages or any shared hosting plan. Firestore's free Spark plan covers learning projects and small apps. Note that deploying Cloud Functions requires the pay-as-you-go Blaze plan (it still includes a free allowance); if you'd rather avoid that, run your Node code on a separate host such as Render.


Mistakes to avoid

  • Forgetting to unsubscribe - every onSnapshot in a useEffect must return its unsubscribe function, or you'll leak listeners and pay for extra reads.
  • Shipping with test-mode security rules. Write real rules before anyone else uses the app.
  • Putting a service-account key in frontend code. Admin credentials belong only in Node code on a server.
  • Listening to entire collections instead of limited, filtered queries.
  • Designing data like SQL tables. Shape documents around the screens that read them.

Take the quiz All picks