Building the Backend of Chatty - A Real-Time MERN WebSocket Chat App

posted 3 min read

Chatty is a real-time chat application built using the MERN stack and WebSockets to enable seamless and instant communication. A strong backend is essential for ensuring real-time messaging, authentication, and database management, creating a reliable and scalable chat experience.

In this blog, I’ll cover:

  • ✅ Setting up WebSockets with socket.io for real-time updates
  • ✅ JWT authentication for secure user sessions
  • ✅ Storing messagesvefficiently in MongoDB

This is only an overview of the code; for the complete implementation, check out the GitHub repo linked at the end!

1. Setting Up the Backend

The backend is built using Node.js & Express.js. To get started, install the necessary dependencies:

npm init -y
npm install express mongoose dotenv socket.io jsonwebtoken bcryptjs cors

Initializing the server:

import express from "express"; import http from "http"; import { Server } from "socket.io"; import mongoose from "mongoose"; import dotenv from "dotenv"; import cors from "cors"; import authRoutes from "./routes/authRoutes.js"; import chatRoutes from "./routes/chatRoutes.js"; dotenv.config(); const app = express(); const server = http.createServer(app); const io = new Server(server, { cors: { origin: "*" } }); app.use(express.json()); app.use(cors()); app.use("/api/auth", authRoutes); app.use("/api/chat", chatRoutes); server.listen(5000, () => console.log("Server running on port 5000"));

2. Implementing WebSocket for Real-Time Messaging

WebSockets allow for bi-directional real-time communication, ensuring that messages are instantly delivered to all connected users.

`io.on("connection", (socket) => {

console.log("User connected:", socket.id);
socket.on("sendMessage", (messageData) => {
    io.emit("receiveMessage", messageData); // Broadcast message to all clients
});
socket.on("disconnect", () => {
    console.log("User disconnected");
});

}); `

3. Implementing JWT Authentication

To ensure user security, we implement JWT-based authentication for login and protected routes.

User Model (models/User.js)

`import mongoose from "mongoose";
import bcrypt from "bcryptjs";
const UserSchema = new mongoose.Schema({

username: { type: String, required: true, unique: true },
password: { type: String, required: true }

});
UserSchema.pre("save", async function (next) {

if (!this.isModified("password")) return next();
this.password = await bcrypt.hash(this.password, 10);
next();

});
export default mongoose.model("User", UserSchema);
`

Auth Controllers (controllers/authController.js)

`import jwt from "jsonwebtoken";
import bcrypt from "bcryptjs";
import User from "../models/User.js";
dotenv.config();
export const registerUser = async (req, res) => {

try {
    const { username, password } = req.body;
    const newUser = new User({ username, password });
    await newUser.save();
    res.status(201).json({ message: "User registered successfully" });
} catch (error) {
    res.status(500).json({ error: error.message });
}

};
export const loginUser = async (req, res) => {

try {
    const { username, password } = req.body;
    const user = await User.findOne({ username });
    if (!user || !await bcrypt.compare(password, user.password)) {
        return res.status(401).json({ message: "Invalid credentials" });
    }
    const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: "1h" });
    res.json({ token, user });
} catch (error) {
    res.status(500).json({ error: error.message });
}

}; `

4. Storing Messages in MongoDB

Chat messages need to be stored persistently so users can retrieve past conversations.

Message Model (models/Message.js)

`import mongoose from "mongoose";
const MessageSchema = new mongoose.Schema({

sender: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
content: { type: String, required: true },
timestamp: { type: Date, default: Date.now }

});
export default mongoose.model("Message", MessageSchema);`

Message Controller (controllers/chatController.js)

`import Message from "../models/Message.js";
export const saveMessage = async (req, res) => {

try {
    const { sender, content } = req.body;
    const message = new Message({ sender, content });
    await message.save();
    res.status(201).json({ message: "Message saved successfully" });
} catch (error) {
    res.status(500).json({ error: error.message });
}`

Final Thoughts & Contribution

That’s how Chatty’s backend is structured! We covered:

  • Real-time WebSockets for instant messaging
  • JWT authentication to enhance security
  • Efficient message storage in MongoDB

This is only an overview of the code; for the complete implementation, check out the GitHub repo! The full repository contains additional optimizations, error handling, and best practices to ensure the app runs smoothly at scale.

Chatty Repo

If you read this far, tweet to the author to show them you care. Tweet a Thanks
Good Yasir, this is fire!  You nailed the WebSockets and JWT stuff.

Quick Q – as the app grows, do you think Mongo will still hold up with tons of messages, or should we think about switching DBs?
Keep it up, love it!

More Posts

Introduction to Chatty - A Real-Time MERN WebSocket Chat App

Mohammad Yasir - Feb 20

Best Databases to Use with the MERN Stack Apart from MongoDB

Aditya Pratap Bhuyan - Mar 10

Why I'm Building My Personal Brand Before Launching My App

Matheo Gomez - Mar 9

How to Build a Dual-LLM Chat Application with Next.js, Python, and WebSocket Streaming

John - Feb 27

When we construct the digital world to the limits of the best devices, we build a less usable one.

Virtually(Creative) - Mar 13
chevron_left