Skip to content

pankil-soni/conversa

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

74 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Conversa β€” MERN Real-Time Chat Application

MongoDB Express.js React Node.js TypeScript Socket.IO TailwindCSS Amazon S3 Google Gemini Docker

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.


Table of Contents


Features

Authentication & Email Verification

  • 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 Management

  • Update name, about text, and profile picture
  • Change password (old password verification required)
  • 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

Tech Stack

Layer Technology
Frontend React 19, TypeScript, Vite 7, Tailwind CSS v4, shadcn/ui, React Router v7
Backend Node.js, Express.js 4
Database MongoDB (Mongoose 8)
Real-time Socket.IO 4 (server + client)
Authentication JSON Web Tokens (jsonwebtoken), bcryptjs
AI Google Gemini via @google/genai
File Storage AWS S3 (pre-signed POST uploads)
Email Nodemailer (Gmail SMTP) β€” OTP login, email verification, message notifications
Containerisation Docker, Docker Compose

Project Structure

conversa/
β”œβ”€β”€ docker-compose.yml                 # Orchestrates mongo + backend + frontend
β”œβ”€β”€ .env.example                       # Template for all environment variables
β”‚
β”œβ”€β”€ backend/
β”‚   β”œβ”€β”€ Dockerfile
β”‚   β”œβ”€β”€ index.js                       # Express app entry point, HTTP server, Socket.IO init
β”‚   β”œβ”€β”€ db.js                          # MongoDB connection
β”‚   β”œβ”€β”€ secrets.js                     # Environment variable exports
β”‚   β”œβ”€β”€ Controllers/
β”‚   β”‚   β”œβ”€β”€ auth-controller.js         # register, login, OTP login, authUser,
β”‚   β”‚   β”‚                              #   sendVerificationOtp, verifyEmail
β”‚   β”‚   β”œβ”€β”€ conversation-controller.js # create, list, get, togglePin
β”‚   β”‚   β”œβ”€β”€ message-controller.js      # allMessage, delete, bulkHide, star, clear, AI streaming
β”‚   β”‚   └── user-controller.js         # updateProfile, block, S3 presign, user search,
β”‚   β”‚                                  #   deleteAccount, getBlockStatus
β”‚   β”œβ”€β”€ Models/
β”‚   β”‚   β”œβ”€β”€ User.js                    # Full user schema (see Data Models)
β”‚   β”‚   β”œβ”€β”€ Conversation.js            # members, latestmessage, unreadCounts
β”‚   β”‚   └── Message.js                 # seenBy, hiddenFrom, softDeleted, starredBy, replyTo
β”‚   β”œβ”€β”€ Routes/
β”‚   β”‚   β”œβ”€β”€ auth-routes.js
β”‚   β”‚   β”œβ”€β”€ conversation-routes.js
β”‚   β”‚   β”œβ”€β”€ message-routes.js
β”‚   β”‚   └── user-routes.js
β”‚   β”œβ”€β”€ socket/
β”‚   β”‚   β”œβ”€β”€ index.js                   # Socket.IO setup, JWT auth middleware, userSocketMap
β”‚   β”‚   └── handlers.js                # All socket event handlers + email notification trigger
β”‚   β”œβ”€β”€ middleware/
β”‚   β”‚   └── fetchUser.js               # JWT verification middleware for REST routes
β”‚   β”œβ”€β”€ utils/
β”‚   β”‚   └── sendMessageEmail.js        # Fire-and-forget offline message email helper
β”‚   β”œβ”€β”€ jobs/
β”‚   β”‚   └── staleOnlineUsers.js        # Hourly cleanup of stale isOnline flags
β”‚   └── scripts/
β”‚       β”œβ”€β”€ seed-test-users.js
β”‚       └── delete-test-users.js
β”‚
└── frontend/
    β”œβ”€β”€ Dockerfile
    β”œβ”€β”€ nginx.conf                     # SPA fallback + asset caching config
    └── src/
        β”œβ”€β”€ App.tsx                    # Route definitions
        β”œβ”€β”€ pages/
        β”‚   β”œβ”€β”€ Home.tsx
        β”‚   β”œβ”€β”€ Login.tsx              # Password + OTP login tabs
        β”‚   β”œβ”€β”€ SignUp.tsx
        β”‚   β”œβ”€β”€ VerifyEmail.tsx        # Post-login email verification gate
        β”‚   β”œβ”€β”€ Conversations.tsx
        β”‚   β”œβ”€β”€ ConversationDetail.tsx # Chat view with streaming bot support
        β”‚   β”œβ”€β”€ StarredMessages.tsx
        β”‚   β”œβ”€β”€ User.tsx               # Redirect helper
        β”‚   └── UserProfile.tsx        # Profile, password, appearance, notification settings
        β”œβ”€β”€ components/
        β”‚   β”œβ”€β”€ layout/
        β”‚   β”‚   β”œβ”€β”€ DashboardLayout.tsx  # Auth + email-verified guard
        β”‚   β”‚   β”œβ”€β”€ ConversationLayout.tsx
        β”‚   β”‚   └── DashboardSidebar.tsx
        β”‚   β”œβ”€β”€ dashboard/             # Chat-specific components
        β”‚   └── ui/                    # shadcn/ui component library
        β”œβ”€β”€ context/                   # AuthProvider, ChatProvider, ConversationsProvider
        β”œβ”€β”€ hooks/                     # use-auth, use-chat, use-conversations, use-socket
        └── lib/
            β”œβ”€β”€ api.ts                 # Centralised HTTP client
            └── socket.ts              # Socket.IO client setup

Architecture Overview

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

Data Models

User

Field Type Notes
name String 3–50 chars, required
email String unique, lowercase
password String bcrypt hashed
about String bio / status text
profilePic String URL; defaults to ui-avatars.com
isOnline Boolean updated on socket connect / disconnect
lastSeen Date set on disconnect
isEmailVerified Boolean false until OTP verification is completed
emailNotificationsEnabled Boolean controls offline email notifications; default true
isBot Boolean true for AI bot accounts
otp String bcrypt-hashed OTP (shared for login OTP and email verification)
otpExpiry Date OTP expiry timestamp
blockedUsers [ObjectId β†’ User] users this user has blocked
pinnedConversations [ObjectId β†’ Conversation] pinned conversation IDs
isDeleted Boolean soft-delete flag for anonymised accounts

Conversation

Field Type Notes
members [ObjectId β†’ User] participants (always exactly 2)
latestmessage String preview text for chat list
unreadCounts [{userId, count}] per-member unread counter
timestamps auto createdAt, updatedAt

Message

Field Type Notes
conversationId ObjectId β†’ Conversation required
senderId ObjectId β†’ User required
text String required if no imageUrl
imageUrl String required if no text; S3 URL
seenBy [{user, seenAt}] read receipts
hiddenFrom [ObjectId β†’ User] hard-deleted for these users
softDeleted Boolean true = "deleted" tombstone shown to all
starredBy [ObjectId β†’ User] users who starred this message
replyTo ObjectId β†’ Message quoted reply reference
timestamps auto createdAt, updatedAt

REST API Reference

All protected routes require the header auth-token: <JWT>.

Auth β€” /auth

Method Path Auth Description
POST /auth/register β€” Create account + personal bot + initial conversation
POST /auth/login β€” Login with password or OTP ({ email, password } or { email, otp })
POST /auth/getotp β€” Send OTP to email for OTP-based login
GET /auth/me βœ… Get authenticated user profile
POST /auth/send-verification-otp βœ… Send a 10-min verification OTP to the logged-in user's email
POST /auth/verify-email βœ… Verify email with OTP; sets isEmailVerified: true

Conversations β€” /conversation

Method Path Auth Description
POST /conversation βœ… Create or retrieve a conversation
GET /conversation βœ… List all conversations (pinned first, then by updatedAt)
GET /conversation/:id βœ… Get a single conversation
POST /conversation/:id/pin βœ… Toggle pin on a conversation

Messages β€” /message

Method Path Auth Description
GET /message/starred βœ… Get all messages starred by the current user
GET /message/:id βœ… Get all messages in a conversation (marks as seen)
DELETE /message/bulk/hide βœ… Hide multiple messages for self (body: { messageIds })
DELETE /message/:id βœ… Delete a message (body: { scope: "me" | "everyone" })
POST /message/clear/:conversationId βœ… Clear entire chat history for self
POST /message/:id/star βœ… Toggle star on a message

Users β€” /user

Method Path Auth Description
PUT /user/update βœ… Update profile (name, about, profilePic, password, emailNotificationsEnabled)
GET /user/online-status/:id βœ… Get online status of a user
GET /user/non-friends βœ… Paginated, searchable, sortable user discovery
GET /user/presigned-url βœ… Get S3 pre-signed POST URL for image upload
POST /user/block/:id βœ… Block a user
DELETE /user/block/:id βœ… Unblock a user
GET /user/block-status/:id βœ… Get mutual block status between current user and target
DELETE /user/delete βœ… Soft-delete / anonymise the authenticated user's account

GET /user/non-friends Query Parameters

Param Default Options
search "" name or email substring
sort name_asc name_asc, name_desc, last_seen_recent, last_seen_oldest
page 1 integer β‰₯ 1
limit 20 1–50

Socket.IO Events

The socket server requires a valid JWT passed in handshake.auth.token.

Client β†’ Server

Event Payload Description
setup β€” Join personal room; mark user online; notify friends
join-chat { roomId } Join a conversation room; reset unread count; mark all messages seen
leave-chat roomId Leave a conversation room
send-message { conversationId, text?, imageUrl?, replyTo? } Send a message (or trigger AI bot response)
delete-message { messageId, conversationId, scope } Delete a message (scope: "me" | "everyone")
typing { conversationId, typer, receiverId } Broadcast typing indicator
stop-typing { conversationId, typer, receiverId } Broadcast stop-typing

Server β†’ Client

Event Payload Description
user setup userId Confirms setup complete
user-joined-room userId Another user entered the conversation room
receive-message Message New message delivered to room
new-message-notification { message, sender, conversation } In-app push to receiver's personal room when not in the chat
messages-seen { conversationId, seenBy, seenAt } Notifies sender their messages were read
message-deleted { messageId, conversationId, softDeleted, latestmessage } 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_key
GEMINI_MODEL=gemini-3-flash-preview

# ── Email (Gmail SMTP) ────────────────────────────────────────────────────────
# Used for: OTP login, email verification, offline message notifications
EMAIL=your_gmail@gmail.com
PASSWORD=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_name
AWS_ACCESS_KEY=your_aws_access_key
AWS_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

Getting Started

Docker (recommended)

Requires Docker Desktop (or Docker Engine + Compose plugin).

# 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.

# Backend
cd 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:

  1. Fork the repository and create a new branch for your feature or bug fix.
  2. Make your changes with clear commit messages.
  3. Ensure all tests pass and the application runs correctly.
  4. Submit a pull request describing your changes and why they should be merged.

License

MIT β€” see the LICENSE file for details.


About the Author

Built by Pankil Soni

About

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 πŸ€–

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors