How To Build a Real-Time Chat app with MERN Stack and SuprSend Javascript SDK?
Anjali Arya
•
June 23, 2023
TABLE OF CONTENTS
In this comprehensive tutorial, we will guide you through the process of building a real-time chat application using the powerful MERN stack and integrating SuprSend for seamless notifications. By combining the robust features of MongoDB as our database, Express.js for server-side development, React.js for building a dynamic user interface, and Node.js as our runtime environment, we'll create a highly scalable and efficient chat app.
Additionally, we'll leverage the capabilities of SuprSend, a versatile notification platform, to enhance user engagement and provide real-time updates. Some of the business use cases for a real-time chat app are as follows:
Let's start Setting up the backend. We install the necessary dependencies in the server directory.
Copied ✔
cd server
npm init
npm i axios cors express socket.io dotenv bcryptjs
Install Nodemon. Nodemon is a Node.js tool that automatically restarts the server after detecting file changes, and Socket.io allows us to configure a real-time connection on the server.
Configure Nodemon by adding the start command to the list of scripts in the package.json file. The code snippet below starts the server using Nodemon.
Copied ✔
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon index.js"
},
We can now run the server with Nodemon by using the command below.
npm start
If you are not using nodemon, either run the server with the command "node index.js" or modify the script by adding the start command to the list of scripts in the package.json file as shown below
Copied ✔
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node index.js"
},
Modify package.json to make index.js an ECMAScript Module by adding "type": "module".
import express from "express";
import dotenv from "dotenv";
const app = express()
import { createServer } from "http";
import cors from "cors";
const server = createServer(app);
dotenv.config();
const PORT = process.env.PORT || 4000;
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use(cors());
app.get("/", (req, res) => {
res.send("Hey!! This is a sign that the server is running");
});
server.listen(PORT, () => console.log("Server is running on port", PORT));
Setting up the frontend:
Navigate into the frontend folder via your terminal and create a new React.js project.
Copied ✔
cd frontend
npx create-react-app ./
Installing dependencies:
Install Socket.io client API and React Router. React Router is a popular routing library for React applications. It allows us to handle navigation and routing in a declarative manner within the React components.
Delete the redundant files such as the logo and the test files from the React app, and update the App.js file to display Hello World as below. Run npm start to verify the app is working fine.
// This is for creating a SignUp page
import React from "react";
const Register = () => {
const handleRegister = async (e) => {
};
return (
);
};
export default Register;
// This is for creating a Login page
import React from "react";
const Login = () => {
const handleLogin = async (e) => {
};
return (
);
};
export default Login;
Chat Page:
Also, create a folder named pages in src and inside it, create chatpage.js. This page, as the name suggests, is incharge of handling the appearance and functionalities of chatting in app.
mkdir pages
cd pages
touch chatpage.js
We’ll write code in it after a while,
Lets's add the routes and react-toastify imports in app.js. We'll add the routes using Browser Router.
Copied ✔
import "./App.css";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import { useContext } from "react";
import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import Register from "./components/register"; // Add this
import Chatpage from "./pages/chatpage";
import Login from "./components/login"; // Add this
function App() {
return (
} />
} />
}
/>
);
}
export default App;
// Also modify index.js file inside src as shown below:
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { BrowserRouter } from "react-router-dom";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
//
//
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
Using Context API:
We will use context api for keeping authentication and user information. Create a folder named context in src, and create context.js inside it.
Now to protect our chatpage route from unauthorized access, we will create a protected route component.
Copied ✔
import { useContext } from "react";
import { AuthContext } from "../context/context";
import {Navigate} from "react-router-dom"
function ProtectedRoute({ children, ...props }) {
const { isAuthenticated } = useContext(AuthContext);
if (!isAuthenticated) {
return
}
return children;
}
export default ProtectedRoute;
Above part ensures that only a logged in user can navigate to chat page.
Now the App.js will be modified to redirect to chatpage when a user is logged in. If an unauthorised user tries to access the chatpage route, he will be redirected to login page.
Copied ✔
import "./App.css";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import { useContext } from "react";
import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import Register from "./components/register";
import Chatpage from "./pages/chatpage";
import Login from "./components/login";
import { AuthProvider } from "./context/context"; // Add this
import ProtectedRoute from "./components/protectedroute"; //Add this
function App() {
return (
} />
} />
}
/>
);
}
export default App;
Now, we’ll complete login and register components. Let’s modify login and register components to setup the useNavigate hook and handle login and register functionalities:
Also create a .env file in the root directory( the parent directory containing public,src etc ) and add the following content
REACT_APP_URL=http://localhost:4000
Backend Development
Since our basic frontend part is ready, let's move on to the backend.
In this app, we’ll use jwt for authentication and authorization.
Including Mongodb, Mongoose, JWT:
Install mongodb driver, mongoose and jsonwebtoken.
npm install mongoose mongodb jsonwebtoken
* Add the following code in index.js of backend.
Copied ✔
import express from "express";
import dotenv from "dotenv";
const app = express();
import mongoose from "mongoose";
//... Rest of the imports
dotenv.config();
const PORT = process.env.PORT || 4000;
app.use(cors());
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
// Add this
mongoose
.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => console.log("Database connected!"))
.catch((err) => console.error(err));
//... Rest of the code
Creating database schemas and models:
Let's create the models now. Create a folder models. Inside models, create user.js, chat.js, message.js to create schemas for user details, chat details and message and their respective models.
Firstly install validator package. Validator package provides many functions to validate different types of data, such as URLs,emails,, dates, and mobile phone numbers, among others. You can also use it to sanitize data.
npm install validator
Now put the following code in models/user.js:
Copied ✔
import mongoose from "mongoose";
import validator from "validator";
const userSchema = new mongoose.Schema({
name: {
type: String,
required: [true, "Please Provide a Username"],
trim: true,
minlength: 4,
},
email: {
type: String,
required: [true, "Please provide an email"],
unique: true,
trim: true,
validate: {
validator: validator.isEmail,
message: "Please Provide Email",
},
},
password: {
type: String,
required: [true, "Please Provide Password"],
minlength: 8,
trim: true,
},
});
export default mongoose.model("User", userSchema);
// In models/chat.js, enter the below code.
import mongoose from "mongoose";
const chatSchema = new mongoose.Schema(
{
chatName: {
type: String,
trim: true,
},
isGroupChat: {
type: Boolean,
default: false,
},
users: [
{
type: mongoose.Types.ObjectId,
ref: "User",
},
],
latestMessage: {
type: mongoose.Types.ObjectId,
ref: "Message",
},
groupAdmin: {
type: mongoose.Types.ObjectId,
ref: "User",
},
},
{ timestamps: true }
);
export default mongoose.model("Chat", chatSchema);
// Finally for the message model in models/message.js, the following code will be used
import mongoose from "mongoose";
const messageSchema = new mongoose.Schema(
{
sender: {
type: mongoose.Types.ObjectId,
ref: "User",
},
message: {
type: String,
trim: true,
},
chat: {
type: mongoose.Types.ObjectId,
ref: "Chat",
},
},
{
timestamps: true,
}
);
export default mongoose.model("Message", messageSchema);
Creating the controllers:
Create a folder named controllers in backend. This folder will contain three files:
auth.js: this file has the code for login,register and search functions.
chat.js : this file has the code for various functions pertaining to chats, group chats etc.
message.js: This file has the code for message related functions like sending a message and getting all messages
Put the following code in controllers/auth.js. We are also including the code for creating a jwt.
Add the details like JWT_SECRET and JWT_LIFETIME in the .env file. JWT_SECRET is a secret message which can be a string used for encryption and JWT_LIFETIME denotes the time for which jwt is valid. It can have values like 30m,10d etc for 30 minutes, 10 days.
JWT_SECRET= somesecretmessage
JWT_LIFETIME=40m
Enter the following code in the controllers/chat.js file:
Now let's create routes in backend for register, login, message and chat. We'll use express router for this. Create a folder named routes in root directory and create 3 files auth.js, chat.js and message.js inside routes.
Copied ✔
// routes/auth.js will have code something like this
import express from "express";
const router = express.Router();
import { register, login, searchUser } from "../controllers/auth.js";
import authenticateUser from "../middleware/auth.js";
router.route("/register").post(register);
router.route("/login").post(login);
router.route("/users").get(authenticateUser,searchUser);
export default router;
// routes/chat.js has the following routes
import express from "express";
const router = express.Router();
import {
getChat,
getChats,
createGroup,
renameGroup,
removeFromGroup,
addUserToGroup,
} from "../controllers/chat.js";
router.route("/").post(getChat).get(getChats);
router.route("/createGroup").post(createGroup);
router.route("/renameGroup").patch(renameGroup);
router.route("/removeFromGroup").patch(removeFromGroup);
router.route("/addUserToGroup").patch(addUserToGroup);
export default router;
// routes/message.js will have this code.
import express from "express";
import { allMessages, sendMessage } from "../controllers/message.js";
const router = express.Router();
router.route("/:chatId").get(allMessages);
router.route("/").post(sendMessage);
export default router;
Verifying JWT:
Since we are using jwt so will also have to verify the jwt of any user attempting to access the routes. Let’s create a folder named middleware in the parent directory of the project. Inside it, create a file named auth.js. Put the below code inside it.
Copied ✔
import jwt from "jsonwebtoken";
import User from "../models/user.js";
const auth = async (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith("Bearer")) {
return res.status(401).send("Authentication Invalid 1");
}
let token = authHeader.split(" ")[1];
try {
const payload = jwt.verify(token, process.env.JWT_SECRET);
req.user = await User.findById(payload.id).select("-password");
next();
} catch (error) {
return res.status(401).send("Authentication Invalid 2");
// throw new Error("Authentication Invalid");
}
};
export default auth;
// Now let’s import all the necessary files in index.js of backend.
import express from "express";
import dotenv from "dotenv";
const app = express();
import mongoose from "mongoose";
import { createServer } from "http";
import cors from "cors";
import { Server } from "socket.io";
const server = createServer(app);
import authRoute from "./routes/auth.js";
import chatRoute from "./routes/chat.js";
import messageRoute from "./routes/message.js";
import authenticateUser from "./middleware/auth.js";
dotenv.config();
const PORT = process.env.PORT || 4000;
app.use(cors());
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
mongoose
.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => console.log("Database connected!"))
.catch((err) => console.error(err));
app.get("/", (req, res) => {
res.send("Hey!! This is a sign that the server is running");
});
app.use("/auth", authRoute);
app.use("/chat", authenticateUser, chatRoute);
app.use("/message", authenticateUser, messageRoute);
server.listen(PORT, () => console.log("Server is running on port", PORT));
Completing the frontend:
Let’s go back to frontend to create the necessary components.
ChatContainer.js is the main chat window where messages will be visible and it has the following code:
Write the code given below in components/singlechat.js, which handles the functionalities of the chat window and includes the styling for various components like messages,input bar etc. Note that here we will be using socket.io and handling various aspects to ensure real time communication. Also, I have used 2 svg icons here. You can use your own preferred icons and adjust the code accordingly.
{(isSameSender(messages, m, i, user._id) ||
isLastMessage(messages, i, user._id)) && (
)}
{m.message}
))}
);
};
export default ScrollableChat;
Creating a Group:
Now let’s create the components for group chat.The Groupchatmodal component is used to create a group. Inside components/groupchatmodel.js, put the following code:
After that you'll need to update the group details.
Finishing the Backend:
Congrats. We have completed the frontend for our chat app. Let’s complete the backend code also. We’ll firstly import Server from socket.io, then add it to cors, and finally establish the connection with frontend. Then we’ll handle all the aspects of chatting using socket.io. Open index.js and add the code for socket.io and various events related to it.
Copied ✔
import express from "express";
import dotenv from "dotenv";
const app = express();
import mongoose from "mongoose";
import { createServer } from "http";
import cors from "cors";
import { Server } from "socket.io";
const server = createServer(app);
import authRoute from "./routes/auth.js";
import chatRoute from "./routes/chat.js";
import messageRoute from "./routes/message.js";
import authenticateUser from "./middleware/auth.js";
dotenv.config();
const PORT = process.env.PORT || 4000;
app.use(cors());
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
mongoose
.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => console.log("Database connected!"))
.catch((err) => console.error(err));
app.get("/", (req, res) => {
res.send("Hey!! This is a sign that the server is running");
});
app.use("/auth", authRoute);
app.use("/chat", authenticateUser, chatRoute);
app.use("/message", authenticateUser, messageRoute);
const io = new Server(server, {
pingTimeout: 60000,
cors: {
origin: "http://localhost:3000",
},
});
io.on("connection", (socket) => {
console.log("Socket connected "+ socket.id)
socket.on("setup", (userData) => {
socket.join(userData._id);
console.log(userData._id + " connected")
socket.emit("connected");
});
socket.on("join-chat", (room) => {
console.log(room+" joined")
socket.join(room);
});
socket.on("typing", (room) => socket.in(room).emit("typing"));
socket.on("stop-typing", (room) => socket.in(room).emit("stop-typing"));
socket.on("new-message", (newMessageReceived) => {
let chat = newMessageReceived.chat;
if (!chat.users) return console.log(`chat.users not defined`);
chat.users.forEach((user) => {
if (user._id === newMessageReceived.sender._id) return;
console.log("Hey got a message " + newMessageReceived)
socket.in(user._id).emit("message-received", newMessageReceived);
});
});
socket.off("setup", () => {
console.log("Socket disconnected")
socket.leave(userData._id);
});
});
server.listen(PORT, () => console.log("Server is running on port", PORT));
So, finally our app looks something like this:
After logging in:
Sidebar:
After sending and receiving messages:
Chatting in group:
Updating group:
After Admin renamed and added more people to group:
Implementing Suprsend Notifications:
By leveraging SuprSend's powerful push notification capabilities, your chat app can deliver instant alerts and updates directly to users' devices, even when they are not actively using the app. This ensures that your users stay connected and informed, fostering better communication and user engagement.
The benefits of using SuprSend for push notifications in chat apps are numerous. Here are a few key advantages:
Real-time Communication: SuprSend enables instant delivery of push notifications, allowing users to stay connected and engaged with their chat conversations in real-time.
Increased User Engagement: By leveraging push notifications, you can keep users informed about new messages, mentions, or important updates, encouraging them to actively participate in conversations.
Personalized Experiences: SuprSend offers the ability to segment users and send personalized notifications, ensuring that users receive relevant and tailored messages based on their preferences and behaviors.
Retention and Re-engagement: Push notifications serve as a powerful tool to re-engage users and bring them back to your chat app. You can remind users about ongoing conversations, invite them to join groups, or notify them about new features, fostering app retention.
Seamless Integration:SuprSend provides easy-to-use APIs and SDKs that seamlessly integrate with popular chat app frameworks, making it effortless to incorporate push notifications into your existing infrastructure.
For this chat app, we can implement web push notifications. For this, we can use the Suprsend Client javascript SDK.
NOTE: Make sure your website uses https protocol.
Creating a template:
Create a template for web push notifications on suprsend platform.
Creating a workflow:
After the template, create a workflow on the suprsend platform.
The Event Name will be needed for sending notifications in the code.
Suprsend SDK Installation:
Install Suprsend Javascript SDK in the client.
npm i @suprsend/web-sdk
Service worker:
Add service worker file in your client. Create a file named serviceworker.js inside public folder(it has to be publicly accessible) and put the following content inside it:
Initialize the Suprsend SDK. Make sure to initialize it before calling other methods of it. Since we are using create-react-app, initialize it on top of App class in App.js. For initializing SDK, you need WORKSPACE KEY and WORKSPACE SECRET and VAPID KEY. SuprSend creates a Vapid key when your account is created. You can find this key on the Vendors page on SuprSend dashboard. Go to Vendors > Web Push > VAPID Keys page, copy the public VAPID key and paste it here in vapid key object value
Replace WORKSPACE_KEY and WORKSPACE_SECRET with your workspace values. You will get both the tokens from the Suprsend dashboard (Settings page -> "API keys" section).
import suprsend from "@suprsend/web-sdk";
suprsend.init(WORKSPACE KEY, WORKSPACE SECRET, {vapid_key:})
Registering for Web Push notifications:
Register for webpush notifications. Suprsend SDK provides method to register service worker which enables your website to prompt notification permission to your users, if not enabled already.
suprsend.web_push.register_push();
Check for permission:
You can check if the user has granted permission to show push notifications using the below method
suprsend.web_push.notification_permission();
This will return a string representing the current permission. The value can be:
granted: The user has granted permission for the current origin to display notifications.
denied: The user has denied permission for the current origin to display notifications.
default: The user's decision is unknown. This will be permission when user first lands on website.
Create/Identify a new user
You can identify a user using suprsend.identify() method.
Call this method as soon as you know the identity of user, that is after login authentication.
suprsend.identify(unique_id);
Sending Events to SuprSend:
Once the workflow is configured, you can pass the Event Name (NEW_MSG in our case) defined in workflow configuration from the SDK and the related workflow will be triggered. Variables added in the template should be passed as event properties
You can send Events from your app to SuprSend platform by using suprsend.track() method
suprsend.track(event_name, property_obj);
Here property_obj is object (optional). Additional data related to the event (event properties) can be tracked using this field.
After implementing notifications, you will get a notification like this:
Throughout the tutorial, you have learned how to set up a React project, implement user authentication, create chat components, handle messages and notifications, and integrate SuprSend for seamless push notification delivery. These skills and concepts can be extended and customized to fit your specific chat app requirements and preferences.
Remember, push notifications play a crucial role in enhancing user engagement, facilitating real-time communication, and providing a personalized chat experience. By harnessing the capabilities of SuprSend, you can take your chat app to the next level and ensure that your users stay connected and informed at all times.
Now it's time to apply what you have learned, unleash your creativity, and build amazing chat applications that deliver exceptional user experiences. Keep exploring the vast possibilities of React, SuprSend, and other tools available to you, and never hesitate to dive deeper into the world of chat app development.
Happy coding, and may your chat app flourish with seamless push notifications powered by SuprSend!
Share this blog on:
Written by:
Anjali Arya
Product & Analytics, SuprSend
Get a powerful notification engine with SuprSend
Build smart notifications across channels in minutes with a single API and frontend components
By clicking “Accept All Cookies”, you agree to the storing of cookies on your device to enhance site navigation, analyze site usage, and assist in our marketing efforts. View our Privacy Policy for more information.