Real-time-chat-application (Backend)


What we will be learning :-
This application is real time chat application what i will be learning
1. Real time interaction using websocket
2. Developing frontend using the react
3. using tailwind css , zustand, react , dazzyUI
4. nodejs ,expressjs, socketIO , mongodb
Application will be having
1 - signup and login page and once you authenticate
2 - Home page which having the conversation and online users / offline users
3 - we can send the text messages, images
4 - from profile we can update the image , in setting page we can select 32 different themes
5 - If someone logout we can see the offline status immediately
6 - Beside this we will be building some loading scalten , error messages etc
We Using the JWT TOKEN
Project Setup - Backend
Backend -
Now lets setup for backend so first you need to go to the backend folder
now lets initialize the node application run the command npm init -y
❯ npm init -y Wrote to /Users/vishal/Desktop/chat-app/backend/package.json: { "name": "backend", "version": "1.0.0", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "description": "" }
Now after this step lets install the dependency npm i express mongoose dotenv jsonwebtoken bcryptjs cookie-parser cloudinary socket.io
Also install the npm i nodemon -D
"dependencies": { "bcryptjs": "^3.0.2", "cloudinary": "^2.7.0", "cookie-parser": "^1.4.7", "dotenv": "^17.2.0", "express": "^5.1.0", "jsonwebtoken": "^9.0.2", "mongoose": "^8.16.4", "socket.io": "^4.8.1" }, "devDependencies": { "nodemon": "^3.1.10" }
Now Our entry file is index.js lets create that inside backend folder and also add the inside package.json so that we can import express rather then using required
Also script added to run server "dev":"nodemon backend/index.js" and run the command npm run dev
"type": "module", "scripts": { "dev":"nodemon backend/index.js" },
Now lets follow the best practice - Backend/Frontend structure
Now we are creating folder call src and in src we putting controller , lib , middleware , models , routes so now the script path and main path is changes
"main": "src/index.js",
"scripts": {
"dev":"nodemon src/index.js"
},
Starter configuration for Building routes for Authentication
Now I am creating the router inside router folder with auth.route.js naming convention
Creating controller where we putting logic of what happen user go to signup or login or logout path
in index.js we putting the authRouter file
// auth.controller.js
// # signup controller
export const signup = (request,response)=>{
response.send("Signup done")
}
// # login controller
export const login = (request,response)=>{
response.send("login done")
}
// # logout controller
export const logout = (request,response)=>{
response.send("logout done")
}
// auth.route.js
import express from 'express';
import { login, logout, signup } from '../controller/auth.controller.js';
const router = express.Router();
router.post('/signup', signup);
router.post('/login',login)
router.post('/logout',logout)
export default router;
// index.js
import express from 'express';
import authRoutes from './routes/auth.route.js'
const app = express();
app.use("/api/auth" , authRoutes);
app.listen(5001,()=>{
console.log(`Server is running 5001`)
});
DATABASE SETUP :
Go monogodb.com / click on new project / provide name as per your need (chat-app)/ click create project/ / click on create inside cluster / select free plan / Provide the name cluster if wanted / Then click create deployment
IMPORTANT : NOW COPY THE PASSWORD WHICH YOU GONE USE INSIDE THE .ENV FILE WHEN YOU WILL IMPORTING THE DATABASE STRING
Copy the connection string from mongoDB database and use it .env with MONGO_URI (you can provide any name) and PORT
Also remember that you need to provide the database name after .net/ or else it will take test as default database name
NOW we setup database now lets see how connect DATABASE using the connection string
- Now lets create the file inside lib db.js
import mongoose from "mongoose";
export const connectDB = async () => {
try {
const conn = await mongoose.connect(process.env.MONGODB_URI);
// This will provde you connected host
console.log(`monogoDb connected ${conn.connection.host}`);
} catch (error) {
console.log("monogoDb connection error :", error);
}
};
// use this file inside the index.js
import { connectDB } from './lib/db.js';
app.listen(5001,()=>{
console.log(`Server is running ${PORT}`)
connectDB()
;});
CREATING SCHEMA FOR USER :
Now we have data schema for users and messges
Now we are creating schema for USER
In the model folder we creating file user.model.js where we are providing schema to it
// user.model.js
import mongoose from "mongoose";
const userSchema = new mongoose.Schema(
{
email: {
type: String,
required: true,
unique: true,
},
fullName: {
type: String,
required: true,
},
password: {
type: String,
required: true,
minlength: 6,
},
profilePic: {
tyep: String,
default: "",
},
},
{
timestamps: true,
}
);
const User = mongoose.model("User", userSchema);
export default User;
Now lets see the design for authentication flow (signUp and login )
In this diagram we saying that how the signup/login controller will be design
Inside the auth.controller.js we writting the logic for signup / login
First we need understand for sign up what we going to send (fullName , email, password , profilepic)
So now we send fullname , email , passoword as request but how we grab that is thing right so
in index.js we use res.use(express.json()) // this middleware we use that allow to extract JSON data from body
NOTE : Check the typo mistake and req.body
Inside the lib file created utils.js where i am adding the authentication using JWT token
// utils .js
import jwt from "jsonwebtoken";
export const generateToken = (userID, res) => {
const token = jwt.sign({ userID }, process.env.JWT_SECRET, {
expiresIn: "7d",
});
res.cookie("jwt", token, {
maxAge: 7 * 24 * 60 * 60 * 1000, //MS
httpOnly: true, // Prevent XSS attack from cross-site scripting attacks
samesite: "strict", // CSRF attacks cross-sites request forgery attacks
secure: process.env.NODE_ENV != "development",
});
return token;
};
//////////////////////////////
// auth.controller.js for SIGNUP logic
import { generateToken } from "../lib/utils.js";
import User from "../models/user.model.js";
import bcrypt from "bcryptjs";
export const signup = async (request, response) => {
const { fullName, email, password } = request.body;
try {
// checking that if the all the field there are not
if (!fullName || !email || !password) {
return response.status(400).json({ message: "All fields are mandatory" });
}
// Checks that password is less then 6 length
if (password.length < 6) {
return response
.status(400)
.json({ message: "Password must be atleast 6 character" });
}
// checks that whether that this email is available or not
const user = await User.findOne({ email });
if (user) {
return response.status(400).json({ message: "Email already exists" });
}
// This below code we used to hash the password in encrypted
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(password, salt); // this will convert our general password in encrypted
// creating new user with email , full name , password with hashed one
const newUser = new User({
email,
fullName,
password: hashedPassword,
});
if (newUser) {
// Generate JWT token here
generateToken(newUser._id, response);
await newUser.save();
response.status(201).json({
_id: newUser._id,
fullName: newUser.fullName,
email: newUser.email,
profilePic: newUser.profilePic,
});
} else {
return response.status(400).json({ message: "Invalid user data" });
}
} catch (error) {
console.log("Error in signup controller", error.message);
response.status(500).json({ message: "Internal server error" });
}
};
/////////////////////////////////
// auth.controller.js for LOGIN logic
export const login = async (request, response) => {
const { email, password } = request.body;
try {
const user = await User.findOne({ email });
if (!user) {
return response.status(400).json({ message: "Invalid credential" });
}
const isCorrectPassword = await bcrypt.compare(password, user.password);
if (!isCorrectPassword) {
return response.status(400).json({ message: "Invalid credential" });
}
generateToken(user._id, response);
return response.status(200).json({
_id: user._id,
fullName: user.fullName,
email: user.email,
profilePic: user.profilePic,
});
} catch (error) {
console.log("Error in login controller", error.message);
return response.status(500).json({ message: "Internal server error" });
}
};
// # logout controller
// auth.controller.js for LOGOUT logic
export const logout = async (request, response) => {
try {
await response.cookie("jwt", "", { maxAge: 0 });
return response.status(200).json({ message: "Logged out succefully" });
} catch (error) {
console.log("Error in login controller", error.message);
return response.status(500).json({ message: "Internal server error" });
}
};
CREATING PROTECTING ROUTES UPDATE PROFILE:
Protecting route we are creating for below senario
When user is logged in and then we want to perform some operation
Like updateProfile , checkin on reload
Now we creating route which having protecting route in it
// index.js file
// # Now we are creating routes for updateProfile and checkin (this will happen only when user is logged in)
import { protectedRoute } from "../middleware/auth.middleware.js";
router.put("/update-profile", protectedRoute, updateProfile); // protected route is middlware which user is logged in or
// not if loggedin then go to updateProfile router
// auth.middleware.js
import jwt from "jsonwebtoken";
import User from "../models/user.model.js";
export const protectedRoute = async (request, response, next) => {
try {
const token = request.cookies.jwt;
// Now we are saying that if the token not then provide this error
if (!token) {
return response
.status(401)
.json({ message: "Unauthorized - No Token Provided" });
}
// If we have token then to grab that we need to use the package called cookie parser and use in index.js configure
// now we need to decode the token and grab userId (BECAUSE IN genereate token we passing the userid)
const decoded = jwt.verify(token, process.env.JWT_SECRET);
if (!decoded) {
return response
.status(401)
.json({ message: "Unauthozied : Invalid token" });
}
// finding user in database
const user = await User.findById(decoded.userID).select("-password");
if (!user) {
return response.status(404).json({ message: "User not found" });
}
// Now user is autheticated adding the user to the request and calling next function that is nothing but updateProfile
request.user = user;
next();
} catch (error) {
console.log("Error in Protected middleware", error.message);
return response.status(500).json({ message: "Internal server error" });
}
};
UPLOADING PROFILE :
- Uploading profile we using cloudnary
To configure inside the react application we need to add these thing in .env file
CLOUDNARY_CLOUD_NAME=
CLOUDNARY_KEY=
CLOUDNARY_SECRET=
Configuring cloudinary in vs code
// This configuration file for cloudinary
// cloudinary.js
import { v2 as cloudinary } from "cloudinary";
import { config } from "dotenv";
config();
cloudinary.config({
cloud_name: process.env.CLOUDNARY_CLOUD_NAME,
api_key: process.env.CLOUDNARY_KEY,
api_secret: process.env.CLOUDNARY_SECRET,
});
export default cloudinary;
// ONCE I DONE THIS i need to update the logic for updateProfile in the controller file
// auth.controller.js
export const updateProfile = async (request, response) => {
try {
const { profilePic } = request.body;
const userId = request.user._id;
if (!profilePic) {
return response.statu(400).json({ message: "Profile pic is required" });
}
const uploadResponse = await cloudinary.uploader.upload(profilePic);
const updateUser = await User.findByIdAndDelete(
userId,
{
profilePic: uploadResponse.secure_url,
},
{ new: true }
);
response.status(200).json(updateUser);
} catch (error) {
console.log("error in update profile", error);
return response.status(500).json({ message: "Internal server error" });
}
};
CREATING PROTECTING ROUTES CHECK PROFILE:
This we are creating just to verify on refresh wherther user is authenticated or not
// index.js
// we created to check on refresh if user is authenticated or not
router.get("/check", protectedRoute, checkAuth);
export default router;
// auth.controller.js
// # Check auth controller
export const checkAuth = (request, response) => {
try {
return response.status(200).json(request.user);
} catch (error) {
console.log("Error in checkAuth ", error);
return response.status(500).json({ message: "Internal server Error" });
}
};
CREATING SCHEMA FOR MESSAGE :
In this we are creating main 3 routes
one is for getUsersForSideBar , getMessageBetweenTwoUsers , sendMessagesBetweebUsers
// index .js file app.use("/api/messages", messageRoutes);
// creating the message.model.js import mongoose from "mongoose"; const messageSchema = new mongoose.Schema( { senderId: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true, }, receiverId: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true, }, text: { type: String, }, image: { type: String, }, }, { timestamps: true } ); const Message = mongoose.model("Message", messageSchema); export default Message;
// creating Router for message.route.js import express from "express"; import { protectedRoute } from "../middleware/auth.middleware.js"; import { getUsersForSideBar, getMessageBetweenTwoUsers, sendMessagesBetweebUsers, } from "../controller/message.controller.js"; const router = express.Router(); // # creating get method for users which we are showing in sidebar router.get("/users", protectedRoute, getUsersForSideBar); // # creating router for fetching message between two users router.get("/:id", protectedRoute, getMessageBetweenTwoUsers); // # creating router for sending message between the users router.get("send/:id", protectedRoute, sendMessagesBetweebUsers); export default router;
// for router creating the controller import Message from "../models/message.model.js"; import User from "../models/user.model.js"; import cloudinary from "../lib/cloudinary.js"; // # Fetching every single user but not ourself export const getUsersForSideBar = async (request, response) => { try { // * Now we are fetching the id from protected router // * Protected router provide the user_id const loggedInUserId = request.user._id; // * Now as per requirement we are filtering userid and fetching // * That filterValue excluding the loggedInUserId // * ne- not equalto const filteredUsers = await User.find({ _id: { $ne: loggedInUserId }, }).select("-password"); return response.status(200).json(filteredUsers); } catch (error) { console.log("Error in getUsersForSidebar", error); return response.status(500).json({ message: "Internal server error" }); } }; // # Now this logic will provide get messages between two users export const getMessageBetweenTwoUsers = async (request, response) => { try { // # Grabbing userID to whom i will be chatting to from link const { id: userChatId } = request.params; // # Getting userId from protected routes const sender_my_Id = request.user._id; // # fetches the messgages between the users const messages = await Message.find({ $or: [ { senderId: sender_my_Id, receiverId: userChatId }, { senderId: userChatId, receiverId: sender_my_Id }, ], }); return response.status(200).json(messages); } catch (error) { console.log("Error in getMessages controller ", error.message); return response.status(500).json({ message: "Internal server error" }); } }; // # Send the messages between users export const sendMessagesBetweebUsers = async (request, response) => { // # Messages which we allow to send is Text , image keep it mind try { const { text, image } = request.body; // # getting image from body const { id: receiverId } = request.params; // # getting receiver id from url param const sender_message_my_Id = request.user._id; // # getting sender id from protected routes // # checking user passing image or not // # if user passing image then we need to upload to cloudinary let imageUrl; if (image) { // upload base 64 image to cloudinary const uploadResponse = await cloudinary.uploader.upload(image); imageUrl = uploadResponse.secure_url; } const newMessage = new Message({ senderId, receiverId, text, image: imageUrl, }); await newMessage.save(); // todo: Realtime functionality goes here => socket.io return response.status(201).json(newMessage); } catch (error) { console.log("Error in sending messge ", error.message); return response.status(500).json({ message: "Internal server error" }); } };
Subscribe to my newsletter
Read articles from Kulkarni Vishal directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
