Building Scalable Real-time Apps with React and Firebase: A Chat App Tutorial

Real-time functionality has become an essential part of modern web apps. Users expect experiences that are interactive, collaborative, and always up-to-date. Whether it‘s chatting with friends, collaborating on a document, or tracking deliveries on a map, real-time features can greatly enhance user engagement and satisfaction.

However, building real-time apps comes with unique challenges. Real-time apps need infrastructure for persistent connections, efficient messaging between clients and servers, and seamless state synchronization. Scaling real-time apps is even trickier, as you need to maintain low-latency data flow for a high volume of concurrent users.

In this tutorial, we‘ll explore how to tackle these challenges using ReactJS and Firebase to build a real-time chat app. You‘ll learn how to:

  • Architect a React app with real-time functionality
  • Authenticate users with Firebase Authentication
  • Store and sync data in real-time with Cloud Firestore
  • Deploy and secure your app for production

By the end, you‘ll have a solid foundation for creating your own real-time web apps that are reliable, scalable, and engaging. Let‘s get real-time!

The Rise of Real-time

Real-time technologies have been around for decades, but they‘ve recently exploded in popularity and become more accessible to developers. According to a report by Grand View Research, the global real-time communication market size was valued at USD 4.45 billion in 2019 and is expected to grow at a compound annual growth rate (CAGR) of 43.4% from 2020 to 2027.

Real-time apps span many domains, including:

  • Instant messaging and chat apps
  • Collaborative productivity tools (Google Docs, Figma, etc.)
  • Multiplayer games and esports
  • Live streaming and video conferencing
  • Real-time dashboards for IoT, stock tickers, sports scores, etc.
  • Location tracking and ride-sharing apps

A major factor driving this growth is the widespread adoption of smartphones and mobile internet. Mobile devices enable users to stay connected in real-time, anytime and anywhere. Real-time features are now table stakes for many categories of apps.

Another key enabler has been the advancement of web technologies and frameworks. Modern web stacks, like the MERN stack (MongoDB, Express, React, Node) or the JAMstack (JavaScript, APIs, Markup), have made it easier than ever to build interactive, real-time web experiences.

Why React and Firebase?

While there are many great options for building real-time apps, React and Firebase are a particularly powerful combo:

React is a popular JavaScript library for building user interfaces. Its component-based architecture and declarative syntax make it easy to create reusable UI elements and manage complex application state. React‘s virtual DOM and efficient rendering engine also enable fast, responsive UIs.

Firebase is a comprehensive development platform that provides backend infrastructure for building web and mobile apps. Firebase offers a suite of tools and services for databases, authentication, hosting, cloud functions, and more. Notably, Firebase has robust support for real-time functionality out of the box.

Together, React and Firebase enable developers to build sophisticated real-time apps without managing low-level infrastructure. React focuses on the frontend UI and state management, while Firebase handles the backend heavy lifting of data synchronization and delivery.

Some key benefits of using React and Firebase:

  • Fast development with declarative UI components and managed backend services
  • Automatic data synchronization across clients with minimal code
  • Built-in security rules and authentication for protecting data
  • Effortless scaling with serverless cloud infrastructure
  • Rich ecosystem of libraries and tooling

With the stage set, let‘s dive into building our real-time chat app!

Setting Up Firebase

Detailed Firebase setup instructions omitted for brevity. See previous response for a full walkthrough.

After setting up your Firebase project and enabling Google authentication, your firebase.js config file should look something like this:

import { initializeApp } from ‘firebase/app‘;
import { getAuth } from ‘firebase/auth‘;
import { getFirestore } from ‘firebase/firestore‘;

const firebaseConfig = {
  apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
  authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
  projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
  storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.REACT_APP_FIREBASE_APP_ID,
};

const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);
export const db = getFirestore(app);

A few notes on this setup:

  • We store the Firebase config values as environment variables prefixed with REACT_APP_. This is a convention used by Create React App to embed environment variables in the build.
  • We initialize Firebase using the initializeApp function from the firebase/app module. This establishes the connection between our app and the Firebase backend.
  • We then get references to the specific Firebase services we need (getAuth for authentication, getFirestore for the Firestore database) and export them for use in our React components.

With Firebase initialized, let‘s set up user authentication.

User Authentication

Code sample for Google sign-in omitted for brevity. See previous response for full code.

In our Navbar component, we first get the currently logged in user using the useAuthState hook from react-firebase-hooks. This hook subscribes to the Firebase Auth state and returns the user object if logged in, or null if not.

We then conditionally render either a "Sign In with Google" button or a "Sign Out" button based on the user state. Clicking the sign in button triggers the signInWithRedirect function from the Firebase Auth SDK, which redirects the user to Google‘s OAuth flow. Upon successful login, the user is redirected back to our app with their authenticated state persisted.

With authentication wired up, let‘s dive into the core of our real-time chat: sending and receiving messages.

Real-time Messaging with Firestore

Chat component code omitted for brevity. See previous response for full code.

Our Chat component handles sending new messages and listening for incoming messages.

Sending Messages

When the user submits the new message form, we construct a new message object containing:

  • The message text
  • A createdAt timestamp generated serverside by Firestore
  • The user display name and avatar URL from the authenticated user object
  • The uid of the user which we‘ll use for security rules

We then use the addDoc function from the Firestore SDK to save this message object as a new document in a messages collection. If the collection doesn‘t exist yet, Firestore will automatically create it for us.

Receiving Messages

To display incoming messages in real-time, we set up a subscription to the messages collection using the onSnapshot function. This function takes a query and a callback to be triggered whenever the query result changes (i.e. new messages are added).

We construct our query with the following options:

  • collection(‘messages‘): specifies the collection to query
  • orderBy(‘createdAt‘): orders the messages by their creation timestamp
  • limit(100): caps the number of messages returned to 100 (optional)

In the onSnapshot callback, we transform the query snapshot into an array of message objects, adding the document ID as a property. We then call setMessages to update our component‘s state with the latest messages.

Because onSnapshot is called with the updated message list whenever new messages are added, our chat feed is always synced in real-time across all clients. This is the magic of Firestore!

Displaying Messages

Lastly, we render our messages as a scrollable list, using the uid of the logged in user to conditionally style messages sent by the user differently from received messages.

We also add an avatar image and the user‘s display name to each message. This info is populated from the user‘s Google profile upon login.

Deploying to Production

Deployment details omitted for brevity. See previous response for deployment options.

A critical aspect of deploying a Firebase app to production is setting up proper security rules. By default, Firestore rules allow read/write access only to authenticated users. However, you‘ll likely want to customize these rules based on your app‘s permission model.

For our chat app, we‘ll set up the following rules:

rules_version = ‘2‘;
service cloud.firestore {
  match /databases/{database}/documents {
    match /messages/{messageId} {
      allow read: if request.auth != null;
      allow create: if request.auth != null 
                    && request.resource.data.uid == request.auth.uid
                    && request.resource.data.text is string
                    && request.resource.data.text.size() < 500;
    }
  }
}

These rules specify that:

  • Any authenticated user can read messages
  • Only authenticated users can create new messages
  • The uid of the message must match the uid of the authenticated user
  • The message text must be a string with a maximum size of 500 characters

These rules ensure that users can only send messages as themselves and prevent abuse like extremely long messages that could bloat our database.

Remember to thoroughly test your security rules with different authentication states and permission levels to ensure your app is locked down as intended before launching to real users.

Conclusion

Congratulations! You now have a fully functional, real-time chat app built with React and Firebase. More importantly, you have the foundational knowledge to build all sorts of real-time web apps with this stack.

To recap, we covered:

  • Setting up a new Firebase project and initializing the SDK in a React app
  • Authenticating users with Google sign-in
  • Storing chat messages in Firestore and listening for real-time updates
  • Deploying the app to production and setting up security rules
  • Throughout the tutorial, we touched on key concepts like componentization, reactive state management, serverless infrastructure, and NoSQL data modeling. These are transferable skills that will serve you well in all your web development endeavors.

But this is just the beginning! There are endless ways you can extend and enhance this basic chat app:

  • Add user presence indicators to show who‘s online
  • Implement typing notifications
  • Allow image or file attachments
  • Create multiple rooms or channels
  • Introduce threaded conversations or hashtags
  • Explore more advanced Firestore concepts like indexes, transactions, and pagination
  • Implement offline support and optimistic UI rendering
  • Set up automated testing and deployment workflows

I encourage you to experiment, iterate, and make this app your own. The best way to solidify your learning is to build something you‘re passionate about.

If you get stuck or want to dive deeper, check out these additional resources:

As always, remember that learning to code is a journey. It‘s okay to start simple, make mistakes, and iterate as you go. The key is to stay curious and keep building.

With the power of React and Firebase at your fingertips, there‘s no limit to what you can create. So go forth and build something awesome! Happy coding!

Originally published on my coding blog. Sign up for my newsletter to get notified about new tutorials and courses on React, Firebase, and full-stack development. You can also find me on Twitter and GitHub.

Similar Posts