Opuama Lucky
Posted on April 8, 2024
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
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
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.
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 theuid
field, choose UID and enter any name you wish; nevertheless, you will useuid
as the name. To proceed, click Add Another Field. -
Username
: For theUsername
field, select a text field and name itUsername
. Then, click Add Another Field to continue.
-
Email
: Choose the email field type and name itEmail
. Afterward, click the Finish button to complete the process. If done correctly, you should see the same image displayed below:
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:
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.
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
Follow the prompt, then continue with the following command:
cd my-app
npm install
npm run dev
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
Then, add this path to your tailwind.config.js
file:
module.exports = {
content: ["./src/**/*.{html,js}"],
theme: {
extend: {},
},
plugins: [],
};
Next, go to the index.css
file in your React application and include the following directives:
@tailwind base;
@tailwind components;
@tailwind utilities;
Run the command below to install DaisyUI on your ReactJs app:
npm i -D daisyui@latest
Then, update your tailwind.config.js
file:
export default {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [require("daisyui")],
};
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;
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;
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;
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;
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;
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;
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;
};
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;
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.
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.
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);
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
Click Next.
- Step 3: Optionally, you can install the Firebase CLI by running this command in the terminal:
npm install -g firebase-tools
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);
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);
};
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;
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;
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;
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;
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:
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
Open your React app terminal window and run the following command as well:
npm run dev
The image below demonstrates the success of your chat application:
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
- You can check the complete code here: frontend and Strapi backend.
- https://firebase.google.com/docs/auth
- https://en.wikipedia.org/wiki/Firebase
- https://docs.strapi.io/dev-docs/backend-customization
Posted on April 8, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.