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’s backend is a Node.js process that runs two communication layers inside the same HTTP server. Express handles stateless REST requests for authentication, messaging, and user discovery. Socket.io shares the same underlying http.Server instance, so WebSocket upgrade handshakes arrive on the same port without any proxy or separate service. A JWT-based protectRoute middleware guards every private endpoint, and in production the compiled React application is served as static files so the entire product ships from a single origin.

Server Bootstrap

server.js is the application entry point. It wires together middleware, routes, static file serving, and the database connection in a deterministic order.
import path from "path";
import express from "express";
import dotenv from "dotenv";
import cookieParser from "cookie-parser";

import authRoutes    from "./routes/auth.routes.js";
import messageRoutes from "./routes/message.routes.js";
import userRoutes    from "./routes/user.routes.js";

import connectToMongoDB from "./db/connectToMongoDB.js";
import { app, server } from "./socket/socket.js";

const PORT = process.env.PORT || 4000;
const __dirname = path.resolve();

dotenv.config();

app.use(express.json());
app.use(cookieParser());

app.use("/api/auth",     authRoutes);
app.use("/api/messages", messageRoutes);
app.use("/api/users",    userRoutes);

app.use(express.static(path.join(__dirname, "/frontend/dist")));

app.get("*", (req, res) => {
  res.sendFile(path.join(__dirname, "frontend", "dist", "index.html"));
});

server.listen(PORT, () => {
  connectToMongoDB();
  console.log(`Server Running on port ${PORT}`);
});
app and server are imported from socket/socket.js, not created in server.js. This ensures the Socket.io instance is attached to the HTTP server before any route or middleware is registered.

Route Structure

PrefixRouter fileProtected
/api/authroutes/auth.routes.jsNo — handles signup, login, and logout
/api/messagesroutes/message.routes.jsYes — protectRoute middleware applied
/api/usersroutes/user.routes.jsYes — protectRoute middleware applied
Auth routes are intentionally public because they are the mechanism by which credentials are exchanged for a JWT cookie. All other routes require a valid token.

JWT Middleware (protectRoute)

protectRoute is an Express middleware that runs before every handler in the messages and users routers.
  1. Reads req.cookies.jwt (set as an httpOnly cookie by the login/signup handlers).
  2. Verifies the token against process.env.JWT_SECRET.
  3. Looks up the user in MongoDB using the userId claim, explicitly excluding the password field with .select('-password').
  4. Attaches the result to req.user and calls next().
If the token is missing, expired, or invalid, the middleware responds with 401 Unauthorized and the handler never executes.
// Simplified protectRoute logic
const decoded  = jwt.verify(token, process.env.JWT_SECRET);
const user     = await User.findById(decoded.userId).select("-password");
req.user = user;
next();
Never remove .select("-password") from this query. The req.user object is sometimes forwarded to the client — omitting the field at the database layer prevents accidental password hash exposure regardless of what the controller does downstream.

Static File Serving and the Catch-All Route

app.use(express.static(path.join(__dirname, "/frontend/dist")));

app.get("*", (req, res) => {
  res.sendFile(path.join(__dirname, "frontend", "dist", "index.html"));
});
express.static serves the Vite build artefacts (JS bundles, CSS, images) directly from the filesystem. The wildcard GET * catch-all sits after all API routes and returns index.html for any path that was not matched earlier. This is what allows React Router to manage client-side URLs like /login or /signup — the browser always receives the SPA shell, and routing happens in JavaScript.
Always register API routes before the static middleware and catch-all. If the catch-all were registered first, every /api/* request would return index.html instead of JSON.

Socket.io Server

The Socket.io server is created in backend/socket/socket.js and exported alongside app and server.
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"],
  },
});

const userSocketMap = {}; // { userId: socketId }

export const getReceiverSocketId = (receiverId) => userSocketMap[receiverId];

io.on("connection", (socket) => {
  const userId = socket.handshake.query.userId;
  if (userId !== "undefined") userSocketMap[userId] = socket.id;

  io.emit("getOnlineUsers", Object.keys(userSocketMap));

  socket.on("disconnect", () => {
    delete userSocketMap[userId];
    io.emit("getOnlineUsers", Object.keys(userSocketMap));
  });
});

export { app, io, server };
Key design decisions:
  • Shared HTTP server — because io wraps the same server that Express runs on, WebSocket upgrades happen on the same port as REST calls. No reverse-proxy rules are needed to route ws:// traffic separately.
  • userSocketMap — an in-memory object that maps each logged-in userId to their current socket ID. Message controllers call getReceiverSocketId(receiverId) to find the target socket and emit newMessage directly to that connection.
  • Online-user broadcast — every time a socket connects or disconnects, io.emit("getOnlineUsers", ...) broadcasts the updated list to all clients. The frontend’s SocketContext listens for this event and updates the onlineUsers array.

Exported Symbols

socket.js is the authoritative source for three exports used across the backend:
ExportTypeUsed by
appExpress applicationserver.js — middleware and route registration
serverhttp.Serverserver.jsserver.listen(PORT, ...)
ioSocket.io ServerMessage controller — io.to(socketId).emit(...)
getReceiverSocketIdFunctionMessage controller — resolves a userId to a live socket ID

Build docs developers (and LLMs) love