You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A full-stack, production-grade real-time chat application built with the MERN stack and Socket.IO. Features include one-on-one messaging, a personalised AI chatbot powered by Google Gemini, image sharing via AWS S3, email verification, email notifications, and a fully responsive dark/light UI built with React 19, TypeScript, Tailwind CSS v4, and shadcn/ui components.
Register / Login with email and password (bcrypt hashed, JWT issued with 7-day expiry)
OTP Login β request a one-time password sent via Nodemailer / Gmail SMTP; time-limited (5 min), bcrypt-stored
Email verification β after registration (or on first login for existing accounts), users must verify their email with a 6-digit OTP before accessing the dashboard; unverified users are always redirected to /verify-email
Persistent sessions β JWT stored in localStorage; auth-token header used on every API call
Account deletion β soft-anonymises the account (clears name, email, bio, credentials) while preserving conversation history for other participants
Profile pictures uploaded directly from the browser to AWS S3 via pre-signed POST URLs (max 5 MB, images only); removal resets to a generated ui-avatars.com URL
Messaging
Real-time one-on-one chat over Socket.IO
Text and image messages β images uploaded to S3 with optional caption text
Reply to message β replyTo reference stored per message; displayed as quoted context in the UI
Delete for me β hard-removes a message from your view only (appended to hiddenFrom)
Delete for everyone β soft-delete sets softDeleted: true; message shows as "This message was deleted" tombstone for all members
Bulk hide β hide multiple selected messages at once for yourself
Clear chat β hide the entire conversation history from your view with a single action
Star / unstar messages β bookmark individual messages; view all starred messages in a dedicated page
Seen receipts β seenBy array tracks who read each message and when
Unread counts β per-user counters maintained on the Conversation document, reset on room join
Latest message preview β latestmessage field keeps the chat list up to date in real time
AI Chatbot
Every user gets a personal AI Chatbot conversation created automatically at registration
Powered by Google Gemini (via @google/genai) with configurable model
Streaming responses β bot replies are streamed chunk-by-chunk over Socket.IO (bot-chunk, bot-done) so text appears progressively
Context-aware β last 19 text messages sent as chat history on every request, giving the bot memory of the conversation
Typing indicator β bot emits typing / stop-typing while generating
Rollback on error β if the Gemini stream fails, the user message is deleted and bot-error is emitted
Email Notifications
When a message is received and the recipient is completely offline (no open sockets), a branded HTML email is sent with a message preview and a deep-link back to the conversation
Fire-and-forget β the email is never awaited in the socket path, adding zero latency to message delivery
Users can toggle email notifications on/off from the Settings page (/user/profile); preference is persisted to the database
Real-Time Presence & Notifications
Online / Offline status β isOnline flag updated on socket connect/disconnect; broadcast to all conversation partners
Last seen β timestamp recorded on disconnect, served via API
Multi-device / multi-tab aware β Map<userId, Set<socketId>> tracks all open sockets; user is only marked offline when their last socket closes
Stale online cleanup β background cron job runs every hour to force-offline users whose socket disconnect was missed (e.g. server crash)
Typing indicators β typing / stop-typing events broadcast to the conversation room and to the receiver's personal room if they are online but not viewing that chat
In-app push notification β new-message-notification event sent to the receiver's personal room when they are not inside the active conversation
Conversation Management
Start a conversation β search for any registered user; reuses an existing conversation if one already exists
Conversations list β sorted by updatedAt descending; pinned conversations always appear at the top
Pin / unpin conversations β per-user; stored as pinnedConversations array on the User document
Block / unblock users β blockedUsers array on the User document
Blocked users cannot send messages (checked server-side before every send-message socket event)
Blocked users see sanitised profile information (generic name, avatar, and offline status)
User discovery β paginated, searchable, and sortable list of users with whom you have no existing conversation
UI & UX
React 19 with full TypeScript type safety
Tailwind CSS v4 with shadcn/ui component library
Dark / Light / System theme toggle powered by next-themes
Fully responsive β optimised for both desktop and mobile
React Router v7 nested route layout system (DashboardLayout β ConversationLayout)
Sonner toast notifications
Markdown rendering in bot messages via react-markdown + remark-gfm
Browser ββHTTPβββΆ Express REST API βββΆ MongoDB
ββWSβββββΆ Socket.IO Server βββΆ MongoDB
βββΆ Gmail SMTP (offline email notifications)
Socket.IO authentication
Every socket connection presents a JWT in handshake.auth.token.
The middleware verifies the token and attaches socket.userId.
Handlers never trust any client-supplied user ID.
Per-user socket tracking
userSocketMap: Map<userId, Set<socketId>>
Tracks all open connections across multiple tabs and devices.
A user is marked offline only when their last socket disconnects.
Email notification pipeline
send-message event βββΆ receiver has no open sockets?
βββΆ receiver.emailNotificationsEnabled?
βββΆ sendMessageEmail() (fire-and-forget, no await)
AI streaming pipeline
Browser ββsend-messageβββΆ Server detects isBot member
βββbot-chunkβββββ streams Gemini chunks via Socket.IO
βββbot-doneββββββ final saved Message document
Tombstone broadcast for scope="everyone"; sidebar preview updated
message-blocked
{ conversationId }
Message rejected due to a block
typing
{ conversationId, typer, receiverId? }
Forwarded typing indicator
stop-typing
{ conversationId, typer, receiverId? }
Forwarded stop-typing indicator
user-online
{ userId }
A contact came online
user-offline
{ userId }
A contact went offline
bot-chunk
{ conversationId, tempId, chunk }
Streamed AI response text chunk
bot-done
{ conversationId, tempId, message }
AI response complete; message is the saved document
bot-error
{ conversationId, userMessageId? }
AI response failed; provides rolled-back message ID
Environment Variables
A single .env file at the project root is used for both Docker Compose and local development. Copy .env.example to .env and fill in your values.
# ββ Database ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ# Overridden automatically by docker-compose to point at the mongo service.MONGO_URI=mongodb://localhost:27017/MONGO_DB_NAME=conversa# ββ Auth ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββJWT_SECRET=change_me_to_a_long_random_secret# ββ Google Gemini (AI bot) ββββββββββββββββββββββββββββββββββββββββββββββββββββGEMINI_API_KEY=your_gemini_api_keyGEMINI_MODEL=gemini-3-flash-preview# ββ Email (Gmail SMTP) ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ# Used for: OTP login, email verification, offline message notificationsEMAIL=your_gmail@gmail.comPASSWORD=your_gmail_app_password# use a Gmail App Password, not your account password# ββ CORS βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββCORS_ORIGIN=*# restrict to your frontend origin in production# ββ AWS S3 (profile picture uploads) βββββββββββββββββββββββββββββββββββββββββAWS_BUCKET_NAME=your_s3_bucket_nameAWS_ACCESS_KEY=your_aws_access_keyAWS_SECRET=your_aws_secret_key# ββ App URL (used in email notification deep-links) βββββββββββββββββββββββββββFRONTEND_URL=http://localhost:5173# ββ Frontend (Vite β baked into the JS bundle at build time) βββββββββββββββββ# Must be the public URL where the backend is reachable FROM THE BROWSER.VITE_API_URL=http://localhost:5500
# 1. Clone the repo
git clone https://github.com/your-username/conversa.git
cd conversa
# 2. Create your .env from the template
cp .env.example .env
# Edit .env β set JWT_SECRET, GEMINI_API_KEY, EMAIL, PASSWORD, AWS_*, etc.# 3. Build and start all three services (mongo + backend + frontend)
docker compose up --build -d
# Frontend β http://localhost# Backend β http://localhost:5500# MongoDB β localhost:27019 (mapped away from the default 27017)
VITE_API_URL must be the URL where the backend is reachable from the user's browser.
For local Docker this is http://localhost:5500. For production, use your public API domain.
Manual (local development)
Requires Node.js β₯ 20 and a running MongoDB instance.
# Backendcd backend
cp .env.example .env # or edit backend/.env directly
npm install
npm run dev # nodemon β listens on :5500# Frontend (separate terminal)cd frontend
# create frontend/src/.env with: VITE_API_URL=http://localhost:5500
npm install
npm run dev # Vite dev server β listens on :5173
Scripts
Backend (backend/)
Script
Command
Description
start
node index.js
Start production server
dev
nodemon index.js
Start dev server with hot-reload
seed:users
node scripts/seed-test-users.js
Seed a set of test users
delete:users
node scripts/delete-test-users.js
Remove seeded test users
Frontend (frontend/)
Script
Command
Description
dev
vite
Start Vite dev server
build
tsc -b && vite build
Type-check + production build
preview
vite preview
Preview the production build locally
lint
eslint .
Run ESLint
format
prettier --write
Format all TS/TSX files
typecheck
tsc --noEmit
Type-check without emitting
Security Design
JWT β tokens are signed with JWT_SECRET, expire after 7 days, and are verified on every protected REST route and every socket connection
No trusted client IDs β senderId is always taken from the verified JWT (socket.userId), never from the client payload
bcrypt β passwords and OTPs are hashed with bcrypt before storage
Block enforcement β the server checks block status before processing every send-message event; a blocked sender receives message-blocked instead
Conversation membership β every join-chat and send-message handler verifies the authenticated user is a member of the target conversation
Email verification gate β the DashboardLayout component redirects unverified users to /verify-email before they can access any chat functionality; bot accounts are pre-verified at creation
S3 pre-signed uploads β the client never receives AWS credentials; uploads go directly to S3 through a short-lived pre-signed POST URL generated server-side
Non-root Docker user β the backend container runs as an unprivileged appuser
Account anonymisation β deleted accounts have credentials wiped and PII replaced with generic values; the document is retained (flagged isDeleted: true) to preserve conversation context for other participants
Background Jobs
staleOnlineUsers (hourly cron)
Runs every hour and sets isOnline: false + updates lastSeen for any user whose isOnline flag is still true but has no active sockets in userSocketMap. This recovers from crash scenarios where the disconnect event was never fired.
Contributing
Contributions are welcome! Please open an issue or submit a pull request with any improvements or bug fixes.
Steps to contribute:
Fork the repository and create a new branch for your feature or bug fix.
Make your changes with clear commit messages.
Ensure all tests pass and the application runs correctly.
Submit a pull request describing your changes and why they should be merged.
A full-stack MERN (MongoDB, Express.js, React.js, Node.js, Socket.IO) chatting application π¬ with friends having features like trending social chatting apps additionally with a Personal Chatbot π€