Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/khushboodaryani/Chat-App/llms.txt

Use this file to discover all available pages before exploring further.

Chat App layers a Socket.io server on top of the same Node.js HTTP server that handles Express REST routes. This means a single process manages both traditional HTTP requests and long-lived WebSocket connections, eliminating the need for a separate real-time service. When a message is sent, the backend emits it directly to the recipient’s socket — the recipient’s browser receives it in milliseconds without polling.

Socket.io Server Setup

The Socket.io server is created by wrapping the Express app in a native http.Server and passing that server to the Server constructor:
// backend/socket/socket.js
import { Server } from "socket.io";
import http from "http";
import express from "express";

const app = express();
const server = http.createServer(app);

const io = new Server(server, {
  cors: {
    origin: ["http://localhost:3000"],
    methods: ["GET", "POST"],
  },
});
Both app (Express) and server (HTTP + Socket.io) are exported from this module so that route files and the entry point can import the same instances.
The CORS configuration allows connections from http://localhost:3000 (the Vite dev server). In production the origin should be updated to match the deployed frontend URL.

Connection Lifecycle

1

Client connects and registers userId

When a user logs in, the frontend opens a socket connection and passes the authenticated user’s _id as a query parameter:
// frontend/src/context/SocketContext.jsx
const socket = io("https://chat-app-yt.onrender.com", {
  query: {
    userId: authUser._id,
  },
});
On the server, the userId is read from socket.handshake.query.userId and stored in userSocketMap:
const userId = socket.handshake.query.userId;
if (userId != "undefined") userSocketMap[userId] = socket.id;
2

Server broadcasts updated online user list

Immediately after storing the mapping, the server emits getOnlineUsers to every connected client with the current set of online user IDs:
io.emit("getOnlineUsers", Object.keys(userSocketMap));
Every client’s SocketContext updates its local onlineUsers array, so presence indicators across all open browser tabs refresh simultaneously.
3

Message is sent to a specific socket

When the message controller needs to deliver a message in real time, it calls getReceiverSocketId to look up the target socket and uses io.to().emit() for a targeted emit:
export const getReceiverSocketId = (receiverId) => {
  return userSocketMap[receiverId];
};

const receiverSocketId = getReceiverSocketId(receiverId);
if (receiverSocketId) {
  io.to(receiverSocketId).emit("newMessage", newMessage);
}
If the receiver is offline their entry will not be in userSocketMap, and the condition short-circuits — the message is still persisted in MongoDB and will appear when the receiver next loads the conversation.
4

Client disconnects and map is updated

When a browser tab is closed or the network drops, the disconnect event fires on the server:
socket.on("disconnect", () => {
  delete userSocketMap[userId];
  io.emit("getOnlineUsers", Object.keys(userSocketMap));
});
The user is removed from the map and the updated online list is broadcast, removing the green indicator from every other client’s sidebar.

Targeted Message Delivery

The userSocketMap object is the core of targeted delivery. It is a simple in-memory dictionary:
// { userId: socketId }
const userSocketMap = {};
By looking up a userId to retrieve a socketId, the server can call io.to(socketId).emit(event, data) which routes the event to exactly one connected client rather than broadcasting to all.
// Broadcast to ALL clients
io.emit("getOnlineUsers", Object.keys(userSocketMap));

// Emit to ONE specific client
io.to(receiverSocketId).emit("newMessage", newMessage);
userSocketMap is stored in memory. If the server process restarts — during a deploy, crash, or scale-in event — the map is wiped and all clients must reconnect. For production deployments with multiple server instances, use the Socket.io Redis adapter to share socket state across processes.

Frontend: SocketContext

The frontend manages the socket connection through a React context provider so that any component can access socket and onlineUsers via the useSocketContext() hook:
// frontend/src/context/SocketContext.jsx
export const SocketContextProvider = ({ children }) => {
  const [socket, setSocket] = useState(null);
  const [onlineUsers, setOnlineUsers] = useState([]);
  const { authUser } = useAuthContext();

  useEffect(() => {
    if (authUser) {
      const socket = io("https://chat-app-yt.onrender.com", {
        query: { userId: authUser._id },
      });

      setSocket(socket);

      socket.on("getOnlineUsers", (users) => {
        setOnlineUsers(users);
      });

      return () => socket.close(); // clean up on authUser change or unmount
    } else {
      if (socket) {
        socket.close();
        setSocket(null);
      }
    }
  }, [authUser]);

  return (
    <SocketContext.Provider value={{ socket, onlineUsers }}>
      {children}
    </SocketContext.Provider>
  );
};
Key behaviours:
  • The connection is opened as soon as authUser is set (login/signup) and closed when authUser becomes null (logout).
  • The effect cleanup function (return () => socket.close()) prevents duplicate connections if authUser changes identity without an intermediate logout.
  • onlineUsers is an array of user ID strings updated in real time by the getOnlineUsers event.
Consuming the context
import { useSocketContext } from "../context/SocketContext";

const MyComponent = () => {
  const { socket, onlineUsers } = useSocketContext();

  // socket — the active Socket.io client instance
  // onlineUsers — string[] of currently connected user IDs
};

Event Reference

Event nameDirectionPayloadDescription
getOnlineUsersServer → All clientsstring[] of user IDsBroadcast whenever any user connects or disconnects
newMessageServer → specific clientMessage documentDelivered to the receiver’s socket when a message is sent
Because newMessage is emitted with io.to(socketId) rather than io.emit(), users only receive messages addressed to them — there is no client-side filtering needed.

Build docs developers (and LLMs) love