Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/abdelhafid37/talkbox/llms.txt

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

TalkBox is structured as two independent processes — a Node.js server and a React client — that communicate over two distinct channels: a conventional HTTP REST API for authentication and data loading, and a persistent WebSocket connection powered by Socket.IO for instant message delivery. Every layer has a single, well-defined responsibility, which keeps the codebase predictable and easy to extend.

System layers

Frontend (React + Vite)

The client is a single-page application built with React 19 and bundled by Vite. It exposes four routes — /, /login, /register, and the JWT-protected /chat — managed by React Router v7. An AuthContext holds the current user and token in memory, a thin api.js Axios instance handles all REST calls, and socket.js exports a single Socket.IO client that is connected and disconnected alongside the user’s session.

Backend API (Express)

The Express 5 server mounts three route groups under /api: /api/auth for registration and login, /api/users for the current user profile and the full user list, and /api/messages for creating and fetching messages. Every route except the auth endpoints is guarded by a JWT authMiddleware that validates the Authorization: Bearer <token> header before the controller runs.

Real-Time Layer (Socket.IO)

Socket.IO is attached to the same HTTP server instance as Express. A middleware step verifies socket.handshake.auth.token before any connection is accepted, so the WebSocket channel is as secure as the REST API. Once connected, clients fire join to register themselves in an in-memory onlineUsers Map, emit sendMessage to send a message, and listen for newMessage and onlineUsers broadcasts from the server.

Message send flow

When a user sends a message, the following sequence occurs end-to-end:
  1. User types and submits — the React UI calls socket.emit("sendMessage", { receiverId, text }) on the client-side Socket.IO instance.
  2. Server receives sendMessage — the sendMessage handler in socket.js picks up the event. The sender’s identity is already attached to socket.user from the authentication middleware, so no extra token check is needed at this point.
  3. Persistence — the handler calls Message.create({ sender: socket.user._id, receiver: data.receiverId, content: data.text }), writing the message to MongoDB.
  4. Population — the newly created document is immediately populated with sender.username and receiver.username so that both parties receive a fully resolved object.
  5. Delivery — the server emits newMessage back to the sender’s socket (socket.emit) and, if the receiver is online, to their socket via io.to(receiverSocketId).emit("newMessage", populatedMessage). Offline recipients will load missed messages through the REST API on their next visit.
// server/src/sockets/socket.js — sendMessage handler
socket.on("sendMessage", async (data) => {
  const message = await Message.create({
    sender: socket.user._id,
    receiver: data.receiverId,
    content: data.text,
  });

  const populatedMessage = await message.populate([
    { path: "sender", select: "username" },
    { path: "receiver", select: "username" },
  ]);

  const receiverSocketId = onlineUsers.get(data.receiverId);

  socket.emit("newMessage", populatedMessage);
  io.to(receiverSocketId).emit("newMessage", populatedMessage);
});

Authentication flow

Authentication is handled entirely over REST and then re-used by the WebSocket layer:
  1. Login — the client POSTs credentials to /api/auth/login. On success the server returns a signed JWT.
  2. Storage — the client stores the token in localStorage and keeps it in AuthContext for the lifetime of the session.
  3. REST requests — every subsequent Axios call attaches the token as an Authorization: Bearer <token> header. The authMiddleware on the server decodes and verifies it before the controller runs.
  4. Socket connection — when the chat page mounts, the client sets socket.auth = { token } and calls socket.connect(). The Socket.IO middleware on the server verifies the same JWT, looks up the user in MongoDB, and attaches the User document to socket.user for the entire connection lifetime.
// client/src/services/socket.js
import { io } from "socket.io-client";

const socket = io(import.meta.env.VITE_API_URL, {
  autoConnect: false, // connected manually after login, with token
});

export default socket;
Because autoConnect is false, the socket is only opened after a successful login, ensuring the JWT is always available before the handshake begins.

Separation of concerns

TalkBox deliberately keeps REST and WebSocket responsibilities separate rather than routing everything through one channel:
  • REST (/api/auth, /api/users, /api/messages) handles stateless, request-response interactions: registering an account, logging in, fetching the user list, and loading conversation history on initial page load. These operations map cleanly onto HTTP semantics and benefit from standard status codes and caching.
  • WebSocket (sendMessage, newMessage, onlineUsers) handles stateful, push-based interactions: delivering messages in real time and broadcasting presence changes. Because the server maintains the onlineUsers Map for the lifetime of the process, it can push to any connected user without the client polling.
This split means the REST layer stays thin and testable in isolation, while the Socket.IO layer stays focused purely on live delivery and presence — neither layer needs to know the implementation details of the other.

Build docs developers (and LLMs) love