How to Build a Chat App with React, Strapi & Firebase

Opuama Lucky - Apr 8 - - Dev Community

Building a chat application with React, Strapi, and Firebase combines React's frontend strength with Strapi backend data management skills, Firebase's authentication and messaging functionality. In this tutorial, you'll learn how to create a Strapi chat app, integrate Firebase for user authentication, and get real-time updates.

Prerequisites

To follow along with this tutorial, make sure you have:

  • NodeJs installation.
  • Basic knowledge of ReactJs.
  • Basic understanding of Firebase.
  • Knowledge of Firebase installation on a React app.

Setting up Strapi Backend

To set up the Strapi project, ensure you have Strapi installed on your system, or you can follow up as the tutorial proceeds with the installation process. Navigate to your terminal and run the command below:

npx create-strapi-app@latest my-project
Enter fullscreen mode Exit fullscreen mode

The above command will create a new project named my-project and install Strapi on your system. After a successful installation, use the following command to launch your Strapi application:

npm run develop
Enter fullscreen mode Exit fullscreen mode

Once you have successfully launched your project, copy the URL from your console and paste it into your browser's address bar. If you're new to Strapi, you must log in or sign up to access your project.

Create Collection Types

After successfully logging in, you'll create an Account collection type to store users who newly join your chat room. Navigate to the side navigation bar, select Content-Type Builder, and click Create new collection type.

001-create-account-collection.png

Clicking Continue will lead you to the next step, where you can add additional fields to your collection. In this step, you'll add three (3) fields to your collection type, each serving a specific purpose as described below:

  • uid: To create the uid field, choose UID and enter any name you wish; nevertheless, you will use uid as the name. To proceed, click Add Another Field.
  • Username: For the Username field, select a text field and name it Username. Then, click Add Another Field to continue.

002-add new field.png

  • Email: Choose the email field type and name it Email. Afterward, click the Finish button to complete the process. If done correctly, you should see the same image displayed below:

003-account-collection-type-fields.png

NOTE: Ensure you click save after creating any collection type

To store users' messages, you need to create a new collection type named ChatRoom-Message with the following fields, as displayed in the image below:

004-chatrooom-type-fields.png

Next, go to Settings, Roles, and click on Public. Then scroll down to Permissions, click on Chat-room-message, select all to authorize the application's activity, and click Save.

005-collection-type-permissions.png

Make sure you do the same with Account and Save.

Building the Chat Room

Since this project will make use of ReactJs, ensure you have ReactJs installed on your system. If not, you can create a new React app by opening your command prompt and running the following command:

npm create vite@latest my-app
Enter fullscreen mode Exit fullscreen mode

Follow the prompt, then continue with the following command:

cd my-app
npm install
npm run dev
Enter fullscreen mode Exit fullscreen mode

The command above will install and run your React application on your localhost server at http://localhost:5173/.

Styling The User Interface

You'll be using DaisyUI for this project, but before incorporating the DaisyUI library into your chat app, you will first need to install Tailwind CSS in your React app. Follow these steps to proceed:

cd my app
npm install -D tailwindcss
npx tailwindcss init
Enter fullscreen mode Exit fullscreen mode

Then, add this path to your tailwind.config.js file:

module.exports = {
  content: ["./src/**/*.{html,js}"],
  theme: {
    extend: {},
  },
  plugins: [],
};

Enter fullscreen mode Exit fullscreen mode

Next, go to the index.css file in your React application and include the following directives:

@tailwind base;
@tailwind components;
@tailwind utilities;
Enter fullscreen mode Exit fullscreen mode

Run the command below to install DaisyUI on your ReactJs app:

npm i -D daisyui@latest
Enter fullscreen mode Exit fullscreen mode

Then, update your tailwind.config.js file:

export default {
  content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
  theme: {
    extend: {},
  },
  plugins: [require("daisyui")],
};
Enter fullscreen mode Exit fullscreen mode

Now that you have installed React and DaisyUI on your system, start your front-end development by creating the navigation bar. Open the my-app directory in a code editor and navigate to the src folder. Create a new folder called Components, then create a file named nav.jsx, and add the following code:

import React from "react";
import { UserAuth } from "../context/AuthContext";

const Nav = () => {
  const { currentUser, logout } = UserAuth();
  const handleLogout = async () => {
    try {
      await logout();
    } catch (error) {
      console.log(error);
    }
  };
  return (
    <div className="navbar bg-primary text-primary-content">
      <div className=" container flex justify-between">
        <a className="btn btn-ghost text-xl">Strapi ChatRoom</a>
        {currentUser ? <button onClick={handleLogout}>Logout</button> : ""}
      </div>
    </div>
  );
};

export default Nav;
Enter fullscreen mode Exit fullscreen mode

In the src folder, create a new folder called pages. Inside it, create a new file named Login.jsx and add the following code:

import React, { useEffect, useState } from "react";
const Login = () => {
  return (
    <div className="hero min-h-screen bg-base-200">
      <div className="hero-content text-center">
        <div className="max-w-md">
          <h1 className="text-5xl font-bold">Welcome to Strapi ChatRoom</h1>
          <p className="py-6">
            Join the conversation, meet new people, and make connections in one
            shared room.
          </p>
          <button className="btn btn-primary">LOGIN WITH GOOGLE</button>
        </div>
      </div>
    </div>
  );
};
export default Login;

Enter fullscreen mode Exit fullscreen mode

Go to your component folder and create a new file named SendMessage.jsx. Add the following code into this file:

import React, { useState } from "react";
const SendMessage = () => {
  const [value, setValue] = useState("");
  const handleSendMessage = (e) => {
    e.preventDefault();
    console.log(value);
    if (value.trim === "") {
      alert("Enter valid message!");
      return;
    }
    setValue("");
  };
  return (
    <div className="bg-gray-200 fixed w-full py-10 shadow-lg bottom-0">
      <form onSubmit={handleSendMessage} className="container flex px-2">
        <input
          className="input w-full focus:outline-none bg-gray-100 rounded-r-none"
          type="text"
          value={value}
          onChange={(e) => setValue(e.target.value)}
        />
        <button className="w-auto bg-gray-500 text-white rounded-r-lg px-5 text-sm">
          Send
        </button>
      </form>
    </div>
  );
};
export default SendMessage;

Enter fullscreen mode Exit fullscreen mode

In this code, when the user submits the form, the handleSendMessage function is triggered. This function stops the form from submitting by default, saves the current message, and checks if it's empty. If empty, it alerts the user to input a message and clears the message input field. Next, create another file named Message.jsx with the following code :

import React from "react";
const Message = ({ message }) => {
  return (
    <div>
      <div className="chat chat-start">
        <div className="chat-image avatar">
          <div className="w-10 rounded-full">
            <img alt="User Avatar" src="" />
          </div>
        </div>
        <div className="chat-header">{message.name}</div>
        <div className="chat-bubble">{message.text}</div>
      </div>
    </div>
  );
};
export default Message;

Enter fullscreen mode Exit fullscreen mode

The above code accepts the message as a prop and renders the username, and message content. Next, create another file named ChatBox.jsx in your Component folder with the following code inside it:

import React from "react";
import Message from "./Message";
import { useState, useRef, useEffect } from "react";
const ChatBox = () => {
  const messagesEndRef = useRef();
  const [messages, setMessages] = useState([]);
  const scrollToBottom = () => {
    messagesEndRef.current.scrollIntoView({ behaviour: "smooth" });
  };
  useEffect(scrollToBottom, [messages]);
  return (
    <div className="pb-44 pt-20 container">
      {messages.map((message, id) => (
        <Message key={id} message={message} />
      ))}
      <div ref={messagesEndRef}></div>
    </div>
  );
};
export default ChatBox;

Enter fullscreen mode Exit fullscreen mode

This code serves as a container for displaying chat messages. To render the ChatBox and SendMessage components, create a new file named chat.jsx inside your pages folder and include the following code:

import React from "react";
import ChatBox from "../Components/ChatBox";
import SendMessage from "../Components/SendMessage";
const Chat = () => {
  return (
    <>
      <ChatBox />
      <SendMessage />
    </>
  );
};
export default Chat;

Enter fullscreen mode Exit fullscreen mode

Next, create a new folder called Routes . Inside this new folder, create a file named PrivateRoute.jsx, and add the following code inside it:

import React from "react";
import { Navigate } from "react-router-dom";
import { UserAuth } from "../context/AuthContext";
export const PrivateRoute = ({ children }) => {
  const { currentUser } = UserAuth();
  if (!currentUser) {
    return <Navigate to="/" replace={true} />;
  }
  return children;
};

Enter fullscreen mode Exit fullscreen mode

The code above defines a private route component that ensures authentication before allowing access to the specified path. Now, to execute your frontend, navigate to App.jsx and replace the existing code with the following:

import Login from "./pages/login";
import Nav from "./Components/Nav";
import Chat from "./pages/Chat";
import { Routes, Route } from "react-router-dom";
import { PrivateRoute } from "./Routes/PrivateRoute";
function App() {
  return (
    <AuthProvider>
      <Nav />
      <Routes>
        <Route path="/" element={<Login />} />
        <Route
          path="/Chat"
          element={
            <PrivateRoute>
              <Chat />
            </PrivateRoute>
          }
        />
      </Routes>
    </AuthProvider>
  );
}
export default App;

Enter fullscreen mode Exit fullscreen mode

Setting up Firebase

Before diving into Firebase setup, understand what Firebase is and how it fits into this project.

According to Wikipedia, Firebase is a suite of back-end cloud computing services and application development platforms offered by Google. It provides databases, services, authentication, and integration for various apps, including Android, iOS, JavaScript, Node.js, Java, Unity, PHP, and C++.

Firebase is a NoSQL database program that stores data in JSON-style documents. In this project, you will use Firebase for user authentication.

Next, log in to Firebase or create an account if you're new. To create a new Firebase project, go to the Firebase Console, click Add project, and follow the prompts. In this tutorial, you'll name your project strapichat. Disable Google Analytics and click Create project.

Click the Web option to begin setting up Firebase. The third icon, which resembles a code icon, represents this option and register the app.

006-firebase-click-web-option.png

Perform the following actions when you click on the Web option:

  • Step 1: Register the app by giving it a nickname and a Firebase hosting which is free.
  • Step 2: Copy the Firebase configuration given to you.

007-copy-firebase-configurations.png

Afterward, create a file named firebase.jsx inside the src folder and paste all the code provided during the Firebase project creation step:

// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
// Your web app's Firebase configuration
const firebaseConfig = {
  apiKey: "AIzaSyCbdtBIaoTLgo1SUEmsfPLHA_4KuptMiTg",
  authDomain: "strapichat.firebaseapp.com",
  projectId: "strapichat",
  storageBucket: "strapichat.appspot.com",
  messagingSenderId: "1025219426688",
  appId: "1:1025219426688:web:912f11e71bb818a6922965",
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);

Enter fullscreen mode Exit fullscreen mode

NOTE: Replace the configurations with the one provided to you by Firebase

The code above initializes Firebase in your React app. Next, run the following command to install the latest SDK:

npm install firebase
Enter fullscreen mode Exit fullscreen mode

Click Next.

  • Step 3: Optionally, you can install the Firebase CLI by running this command in the terminal:
npm install -g firebase-tools
Enter fullscreen mode Exit fullscreen mode

Enable User Authentication

After creating your Firebase project, please navigate to the Firebase Console dashboard and select it. From the left sidebar menu, choose Authentication to access the authentication section. Here, you'll find a list of available authentication methods. Click on Get Started to activate the authentication methods you want for your project.

Firebase offers various authentication mechanisms, including email/password, Google, Facebook, Twitter, and more. You'll be utilizing Google authentication, so select and enable the Google authentication method.

Next, navigate to the firebase.jsx file inside the src folder and replace the code:

// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
// Your web app's Firebase configuration
const firebaseConfig = {
  apiKey: "AIzaSyCbdtBIaoTLgo1SUEmsfPLHA_4KuptMiTg",
  authDomain: "strapichat.firebaseapp.com",
  projectId: "strapichat",
  storageBucket: "strapichat.appspot.com",
  messagingSenderId: "1025219426688",
  appId: "1:1025219426688:web:912f11e71bb818a6922965",
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);  

Enter fullscreen mode Exit fullscreen mode

NOTE: Replace the configuration

In the provided code, you imported getAuth from Firebase, which retrieves a reference to the Firebase Authentication service instance. Next, create a folder named context inside the src directory and then create a new file called AuthContext.jsx, and paste the following code inside it:

import { createContext, useState, useEffect, useContext } from "react";
import {
  GoogleAuthProvider,
  onAuthStateChanged,
  signInWithRedirect,
  signOut,
} from "firebase/auth";
import { auth } from "../firebase";
//create context
const AuthContext = createContext();
//provider context
export const AuthProvider = ({ children }) => {
  const [currentUser, setCurrentUSer] = useState("");
  const [loading, setLoading] = useState(true);
  //   signin with google
  const signInWithGoogle = () => {
    const provider = new GoogleAuthProvider();
    signInWithRedirect(auth, provider);
  };
  // signout from google
  const logout = () => signOut(auth);
  const value = {
    currentUser,
    setCurrentUSer,
    signInWithGoogle,
    logout,
  };
  // set currentUser
  useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, (user) => {
      setCurrentUSer(user);
      setLoading(false);
    });
    return unsubscribe;
  }, []);
  return (
    <AuthContext.Provider value={value}>
      {!loading && children}
    </AuthContext.Provider>
  );
};
export const UserAuth = () => {
  return useContext(AuthContext);
};

Enter fullscreen mode Exit fullscreen mode

The code above sets up a flexible authentication system using Firebase in your React application. It manages the authentication state, initializes state variables for the current user, a loading status, and provides functions for signing in with Google and logging out.

Integrate Strapi with Firebase

In this phase, you will integrate your Strapi backend with Firebase in your React app. Open the Login.jsx file located in the pages folder within your src directory, and add the following code to it:

import React, { useEffect, useState } from "react";
import { UserAuth } from "../context/AuthContext";
import { useNavigate } from "react-router-dom";
const Login = () => {
  const navigate = useNavigate();
  const { currentUser, signInWithGoogle } = UserAuth();
  const [displayName, setDisplayName] = useState("");
  const [uid, setUid] = useState("");
  const [email, setEmail] = useState("");
  const handleLogin = async () => {
    try {
      await signInWithGoogle();
    } catch (error) {
      console.log("Error signing in:", error);
    }
  };
  useEffect(() => {
    // Check if currentUser exists
    if (currentUser) {
      const fetchUserData = async () => {
        try {
          // Destructure currentUser to get necessary details
          const { displayName, uid, email } = currentUser;
          // Update state with the user details
          setDisplayName(displayName);
          setUid(uid);
          setEmail(email);
          // Send user details to Strapi API
          await fetch("http://localhost:1337/api/accounts", {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify({
              data: {
                uid: uid,
                Username: displayName,
                Email: email,
              },
            }),
          })
            .then((response) => response.json())
            .then((data) => {
              console.log("Data from Strapi API:", data);
              // navigate to another page after sending data
              navigate("/Chat");
            })
            .catch((error) => {
              console.log("Error sending user data to Strapi API:", error);
            });
        } catch (error) {
          console.log("Error setting user data:", error);
        }
      };
      // Call fetchUserData function
      fetchUserData();
    }
  }, [currentUser, navigate]); // Include currentUser and navigate in the dependency array
  return (
    <div className="hero min-h-screen bg-base-200">
      <div className="hero-content text-center">
        <div className="max-w-md">
          <h1 className="text-5xl font-bold">Welcome to Strapi ChatRomm</h1>
          <p className="py-6">
            Join the conversation, meet new people, and make connections in one
            shared room.
          </p>
          <button onClick={handleLogin} className="btn btn-primary">
            LOGIN WITH GOOGLE
          </button>
        </div>
      </div>
    </div>
  );
};
export default Login;

Enter fullscreen mode Exit fullscreen mode

The code above handles the login process using Google sign-in via Firebase authentication. It retrieves the user's information, sends it to Strapi if successfully signed, and redirects the user to the Chat page, where users can start communication.

Read Chat Messages

To read chat messages, you need to send a request to Strapi to retrieve messages. To do this, navigate to the Components/ChatBox.jsx file in your src directory and add the following code:

import React from "react";
import Message from "./Message";
import { useState, useRef, useEffect } from "react";
const ChatBox = () => {
  const messagesEndRef = useRef();
  const [messages, setMessages] = useState([]);
  const scrollToBottom = () => {
    messagesEndRef.current.scrollIntoView({ behaviour: "smooth" });
  };
  useEffect(scrollToBottom, [messages]);
  useEffect(() => {
    const interval = setInterval(() => {
      fetch("http://localhost:1337/api/chat-room-messages", {
        method: "GET",
        headers: {
          "Content-Type": "application/json",
        },
      })
        .then((response) => response.json())
        .then((res) => setMessages(res.data));
    }, 5000);
    return () => clearInterval(interval);
  }, []);
  return (
    <div className="pb-44 pt-20 container">
      {messages.map((message, id) => (
        <Message key={id} message={message} />
      ))}
      <div ref={messagesEndRef}></div>
    </div>
  );
};
export default ChatBox;

Enter fullscreen mode Exit fullscreen mode

As explained earlier and as seen in the code above, the ChatBox component displays individual chat messages in the Strapi chatroom, including the username and user photo.

The first useEffect hook is called whenever the messages state changes. It scrolls to the bottom of the chatbox.

The second useEffect hook runs once after the component mounts. It fetches chat room messages from a local server endpoint http://localhost:1337/api/chat-room-messages, every 5 seconds using setInterval. Upon receiving the response, it updates the messages state.

To render your chat messages, navigate to Components/Messages.jsx in your src folder and replace the file with the following code:

import React from "react";
import { UserAuth } from "../context/AuthContext";
const Message = ({ message }) => {
  const { currentUser } = UserAuth();
  return (
    <div>
      <div
        className={`chat ${
          message.attributes.User === currentUser.displayName
            ? "chat-start"
            : "chat-end"
        }`}
      >
        <div className="chat-image avatar">
          <div className="w-10 rounded-full">
            <img alt="User Avatar" src={message.attributes.Photo} />
          </div>
        </div>
        <div className="chat-header">{message.attributes.User}</div>
        <div className="chat-bubble">{message.attributes.message}</div>
      </div>
    </div>
  );
};
export default Message;

Enter fullscreen mode Exit fullscreen mode

Create New Messages

To create and post new messages to Strapi, you must capture user input and send it to your Strapi backend. To accomplish this, navigate to the Components/SendMessage.jsx file in your src directory and insert the following code:

import React, { useState } from "react";
import { UserAuth } from "../context/AuthContext";
const SendMessage = () => {
  const [value, setValue] = useState("");
  const { currentUser } = UserAuth();
  const [displayName, setDisplayName] = useState("");
  const [photoURL, setPhotoURL] = useState("");
  const handleSendMessage = (e) => {
    e.preventDefault();
    console.log(value);
    if (value.trim === "") {
      alert("Enter valid message!");
      return;
    }
    try {
      const { displayName, photoURL } = currentUser;
      if (currentUser) {
        fetch("http://localhost:1337/api/chat-room-messages", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            data: {
              message: value,
              User: displayName,
              Photo: photoURL,
            },
          }),
        })
          .then((response) => response.json())
          .then((data) => console.log(data));
      }
    } catch (error) {
      console.log(error);
    }
    setValue("");
    setDisplayName(displayName);
    setPhotoURL(photoURL);
    console.log(currentUser);
  };
  return (
    <div className="bg-gray-200 fixed w-full py-10 shadow-lg bottom-0">
      <form onSubmit={handleSendMessage} className="container flex px-2">
        <input
          className="input w-full focus:outline-none bg-gray-100 rounded-r-none"
          type="text"
          value={value}
          onChange={(e) => setValue(e.target.value)}
        />
        <button className="w-auto bg-gray-500 text-white rounded-r-lg px-5 text-sm">
          Send
        </button>
      </form>
    </div>
  );
};
export default SendMessage;

Enter fullscreen mode Exit fullscreen mode

The code above allows users to compose and send messages within your chat application. When a user sends a message, the message content, along with the user's display name and photo URL obtained from Firebase authentication, is sent to Strapi using a POST request. This function ensures the message gets stored in the Strapi backend for further processing, retrieval or customization.

Your app should look like this:

009-other-home-screen.png

Demo Time!

Since your application data depends on Strapi, you must start your Strapi server before your application launches. To accomplish this, open a new terminal window, navigate to the directory of your Strapi app, and launch the application by running the command below:

npm run develop
Enter fullscreen mode Exit fullscreen mode

Open your React app terminal window and run the following command as well:

npm run dev
Enter fullscreen mode Exit fullscreen mode

The image below demonstrates the success of your chat application:

008-project-demo.gif

The short clip above shows how users exchange messages, facilitating real-time communication.

Feel free to add more features to enhance the appearance and functionality of your chat app.

Conclusion

Congratulations on fully implementing your Strapi chat app! Building a chat application using React, Strapi, and Firebase combines powerful front-end and back-end technologies. Strapi provides flexibility in handling data and APIs, while React offers a robust foundation for constructing user interfaces. Firebase enhances the application by providing user authentication. With these tools, you can create dynamic and interactive chat applications.

Resources

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .