HelloTalk – A Real-time Chat AppW
A full‑stack real‑time chat app with private/group chats, friend requests, notifications, attachments, and an admin dashboard. This post covers the architecture, MongoDB data modeling, Socket.IO events, security, and tradeoffs behind HelloTalk.
HelloTalk – A Real-time Chat App with Role Management
HelloTalk is a production-grade real-time chat application featuring role-aware moderation, rich messaging, and a modern admin dashboard. It’s built as a monorepo with separate client
and server
apps, following the repository layout in the public codebase.
Repo: HelloTalk on GitHub
Project Name and Context
- Project: HelloTalk (real-time chat)
- Goal: Reliable, safe, and fast messaging for communities and small teams
- Why: Most open chat apps lack fine-grained moderation, a social graph, and admin-first observability
Functionality and Purpose
- Private and Group Chats with typing indicators and online users list
- Friend Requests + Notifications to build a social graph
- Admin Dashboard: users, chats, messages, and a 7‑day message chart
- Attachments (Cloudinary), multi-file upload per message
- Search: find users and filter available members to add to chats
- Moderation: admin verification, basic ban/report flows (server endpoints)
- Modern UI: lazy-loaded routes, toasts, charts, responsive design
Tech Stack Used
- Client: React 19, Vite 7, Redux Toolkit + RTK Query, React Router 7, Socket.IO Client, Tailwind CSS 4, Chart.js
- Server: Node.js, Express 5, TypeScript, Socket.IO 4, Mongoose 8, Zod, Multer, Cloudinary, Winston
- Database: MongoDB (Atlas/self-hosted)
- Auth: HTTP‑only cookies (
user-token
,admin-token
) with rotating expiries and server-side verification - Infra: Docker, docker-compose, Github Actions; Vercel (client) + Railway/Fly.io (services)
Details reflect the repo structure and README: HelloTalk repo
How I Built It
Monorepo layout
hellotalk/
client/ # React app (Vite)
server/ # Node/Express API + Socket.IO
docker-compose.yml # Local Docker setup for both services
Environment & Auth
- Server env:
CLIENT_URL
,MONGO_URI
, Cloudinary keys, JWT secrets, ports - Client env:
VITE_SERVER
for API base URL - Cookies:
secure: true
,sameSite: "none"
– for local HTTP dev you may need HTTPS or relax cookie settings temporarily
MongoDB Data Modeling (Mongoose)
// server/src/app/models/message.model.ts
import { Schema, model } from "mongoose";
const messageSchema = new Schema(
{
conversationId: { type: Schema.Types.ObjectId, ref: "Chat", index: true },
senderId: { type: Schema.Types.ObjectId, ref: "User", index: true },
body: { type: String },
attachments: [{ url: String, type: String, bytes: Number }],
},
{ timestamps: true }
);
export default model("Message", messageSchema);
// server/src/app/models/chat.model.ts
import { Schema, model } from "mongoose";
const chatSchema = new Schema(
{
title: { type: String },
isGroup: { type: Boolean, default: false },
members: [{ type: Schema.Types.ObjectId, ref: "User" }],
createdBy: { type: Schema.Types.ObjectId, ref: "User" },
lastMessageAt: { type: Date },
},
{ timestamps: true }
);
export default model("Chat", chatSchema);
Socket.IO Events
Client and server share event names. Common events (subset):
// server/src/app/constants/events.ts
export const EVENTS = {
NEW_MESSAGE: "NEW_MESSAGE",
NEW_MESSAGE_ALERT: "NEW_MESSAGE_ALERT",
START_TYPING: "START_TYPING",
STOP_TYPING: "STOP_TYPING",
CHAT_JOINED: "CHAT_JOINED",
CHAT_LEAVED: "CHAT_LEAVED",
ONLINE_USERS: "ONLINE_USERS",
ALERT: "ALERT",
REFETCH_CHATS: "REFETCH_CHATS",
NEW_ATTACHMENT: "NEW_ATTACHMENT",
NEW_REQUEST: "NEW_REQUEST",
} as const;
Attachments & Cloudinary
- Upload via Multer on the server (
multipart/form-data
) - Store raw files in Cloudinary with metadata saved on the message
- Purge Cloudinary assets on message deletions
// server/src/app/controllers/chat.controller.ts (snippet)
const upload = multer({ limits: { fileSize: 10 * 1024 * 1024 } });
// handle /chat/message with files -> upload to Cloudinary, persist metadata
Admin Dashboard
- Stats (users/chats/messages), 7‑day chart (Chart.js)
- Server routes under
/admin/*
for session and list endpoints - Admin secret verification + separate cookie (
admin-token
)
Unique Implementations / System Design
- Social Graph: friend requests, acceptance flow, and notifications
- Socket Auth: authenticate sockets using
user-token
cookie, room joins per chat - Lazy + RTK Query: efficient client data fetching and optimistic updates
- Cloudinary-first media with signed URLs and cleanup hooks
API overview and routes summarized from the repo README: HelloTalk
Realistic Challenges or Tradeoffs
- Cookie + CORS in Dev: HTTP‑only cookies require HTTPS or relaxed settings locally; this can confuse local testing
- WebSocket Fan‑out: big rooms require careful partitioning and backpressure
- Attachment Safety: content-type validation and size limits add latency but improve trust
- Search vs Consistency: paginated server queries vs client cache freshness
What I Learned or Improved
- Building a pragmatic real-time system with Socket.IO and cookie auth
- Structuring a monorepo for clarity: client/server separation with shared constants
- Operational basics: logging (Winston), rate limits, and error boundaries
- Admin-first observability with a simple but effective dashboard
Next Steps
- Per-conversation roles and moderator tools beyond admin-only gates
- E2EE experiments for DMs with safety tooling for groups
- Full-text message search and media galleries
- Mobile push notifications and background sync
References: HelloTalk GitHub repository