ThreadX is a full-stack real-time messaging application built for seamless one-on-one and group conversations. Users sign up (or continue with Google), and immediately get access to a conversation-centric workspace: Direct chats, group rooms with admin controls, image sharing, message replies, pinned chats, and live typing indicatorsβall streaming over WebSocket in real time. The app pairs a fast React SPA with a secure Express API backed by MongoDB, using JWT access tokens, httpOnly refresh cookies, automatic token refresh, and Arcjet-powered security on every endpoint.
- React 19 β UI with function components and hooks.
- Vite 7 β Lightning-fast dev server and production builds.
- TypeScript β Typed components, stores, and API layer.
- Tailwind CSS v4 β Utility-first styling.
- React Router v7 β Public vs. protected routes.
- Zustand β Lightweight global state for auth and chat.
- TanStack Query β Server-state management and caching.
- Axios β HTTP client with
Authorizationheaders and refresh-retry interceptor. - Socket.IO Client β Real-time bidirectional messaging.
- Zod β Client-side schema validation.
- Emoji Picker React β In-chat emoji selection.
- React Markdown β Render markdown-formatted messages.
- Sonner β Toast notifications.
- React Icons β Icon set.
- Google OAuth (
@react-oauth/google) β One-tap Google sign-in. - PWA β Installable, offline-capable via
vite-plugin-pwaand Workbox.
- Node.js β Runtime.
- Express 5 β REST API (
/api/auth,/api/user,/api/messages). - TypeScript β Typed controllers, models, and middleware.
- Socket.IO β Real-time event server (online presence, typing indicators, message broadcast).
- MongoDB + Mongoose β User, Conversation, Message, and token persistence.
- JWT β Short-lived access tokens; refresh tokens stored server-side and in httpOnly cookies.
- bcrypt β Password hashing.
- Zod β Request body validation on create/update flows.
- Arcjet β Bot detection, shield protection, and sliding-window rate limiting on every route (
@arcjet/node). - Cloudinary β Signed upload and storage for profile pictures and message images.
- Google Auth Library β Server-side Google ID token verification.
- Nodemailer + Resend β Transactional email for password-reset flows.
- cookie-parser & cors β Cookies and cross-origin configuration for the SPA.
- Authentication β Email/password sign-up and login, plus one-tap Google OAuth. Access token stored in a cookie; refresh token in an httpOnly cookie.
- Forgot / Reset Password β Secure email-based password reset with time-limited tokens.
- Session resilience β Axios interceptor calls
/auth/refreshon 401 and retries the original request; Socket.IO reconnects and re-authenticates transparently. - Direct Messaging β One-on-one conversations with a persistent conversation thread.
- Group Chats β Create named groups with a custom avatar, add/remove members, leave, or delete the group entirely.
- Message Replies β Reply to any message with a linked preview; scroll-to-original on click.
- Image Sharing β Send images in chat via direct Cloudinary upload with signed URL.
- Optimistic UI β Messages are added to the local store immediately with a temporary ID. Once the server confirms, the temp entry is swapped for the real document. On failure the temp message is rolled back and an error toast is shown.
- Unread Counts & Mark as Read β Per-conversation unread badge that clears when the chat is opened.
- Pin Chats β Pin important conversations to the top of the chat list (per user).
- Real-time Typing Indicators β Live "β¦is typing" broadcast to all participants in a conversation.
- Online Presence β Live online user list pushed to all connected clients over WebSocket.
- Delete Messages β Only the sender can delete their own messages; the image is removed from Cloudinary before the document is deleted, and unread counts are reconciled across all participants.
- Chat Deletion (soft-delete) β Deleting a direct chat sets a per-user
hiddenflag on the Conversation document rather than removing any data. The conversation reappears automatically if a new message is sent. Group deletion (admin only) is a hard delete β all messages and Cloudinary assets are purged before the conversation document is destroyed. - Profile Management β Upload a profile picture via Cloudinary signed upload.
- Sound Notifications β Toggleable in-app sound on incoming messages.
- PWA Support β Installable on desktop and mobile; service worker for offline readiness.
- Security β Arcjet shield, bot detection, and per-IP rate limiting on auth routes (10 req/min) and general API routes (100 req/min).
threadx/
βββ backend/ # Express API, Socket.IO server
β βββ src/
β βββ config/ # DB, socket, arcjet, env, cloudinary
β βββ controllers/ # auth, user, message
β βββ middlewares/ # auth, arcjet, origin, socket-auth
β βββ models/ # User, Conversation, Message, tokens
β βββ routes/ # auth, user, message
β βββ schemas/ # Zod validation schemas
β βββ template/ # Email HTML templates
β βββ utils/ # Helpers (token, email, etc.)
β βββ server.ts
βββ frontend/ # React + Vite SPA
βββ src/
βββ api/ # Axios API layer (auth, user, message)
βββ components/ # chat, navigation, shared, ui
βββ config/ # axios instance, route config
βββ hooks/ # Custom React hooks
βββ pages/ # landing, auth, home, error, loading
βββ routes/ # Route definitions & guards
βββ schemas/ # Zod form schemas
βββ store/ # Zustand stores (useAuthStore, useChatStore)
βββ types/ # TypeScript interfaces
βββ utils/ # Utility functions
| Method | Path | Auth | Description |
|---|---|---|---|
POST |
/api/auth/signup |
No | Register with email & password |
POST |
/api/auth/login |
No | Login with email & password |
POST |
/api/auth/google |
No | Login / register via Google OAuth |
POST |
/api/auth/forgot-password |
No | Send password-reset email |
POST |
/api/auth/reset-password |
No | Reset password with token |
POST |
/api/auth/refresh |
No | Exchange refresh cookie for new token |
POST |
/api/auth/logout |
Yes | Invalidate session |
GET |
/api/auth/upload-profile-signature |
Yes | Cloudinary signed upload params |
PATCH |
/api/auth/update-profile |
Yes | Update profile picture URL |
GET |
/api/user/ |
Yes | Get current authenticated user |
GET |
/api/messages/contacts |
Yes | Paginated contact list |
GET |
/api/messages/chats |
Yes | All conversations for current user |
GET |
/api/messages/conversations/:id |
Yes | Messages by conversation ID |
GET |
/api/messages/:id |
Yes | Messages by user ID (direct chat) |
POST |
/api/messages/:id |
Yes | Send a message |
DELETE |
/api/messages/:messageId |
Yes | Delete a message |
POST |
/api/messages/:id/read |
Yes | Mark conversation as read |
PATCH |
/api/messages/:id/pin |
Yes | Toggle pin on a chat |
DELETE |
/api/messages/chats/:id |
Yes | Delete entire direct chat |
POST |
/api/messages/groups |
Yes | Create a group conversation |
PATCH |
/api/messages/groups/:id |
Yes | Update group name / avatar |
DELETE |
/api/messages/groups/:id |
Yes | Delete group (admin only) |
POST |
/api/messages/groups/:id/members |
Yes | Add members to group |
POST |
/api/messages/groups/:id/remove |
Yes | Remove a member from group |
POST |
/api/messages/groups/:id/leave |
Yes | Leave a group |
GET |
/api/messages/upload-message-signature |
Yes | Cloudinary signed params for images |
GET |
/api/messages/upload-group-avatar-signature |
Yes | Cloudinary signed params for group avatar |
| Event | Direction | Description |
|---|---|---|
getOnlineUsers |
Server β Client | Array of currently online user IDs |
newMessage |
Server β Client | Broadcast new message to conversation recipients |
typing:start |
Client β Server | User started typing in a conversation |
typing:stop |
Client β Server | User stopped typing in a conversation |
typing:update |
Server β Client | Typing state for a conversation participant |
unreadUpdate |
Server β Client | Updated unread count for a conversation |
messagesRead |
Server β Client | Conversation marked as read by a participant |
You need Node.js and a MongoDB deployment (local or Atlas). A Cloudinary account is required for media uploads, and Arcjet for security/rate limiting.
git clone https://github.com/Pappyjay23/threadx.git
cd threadxcd backend
npm installCreate backend/.env:
# Server
PORT=5001
NODE_ENV=development
# CORS β set to your frontend origin
CLIENT_URL=http://localhost:5173
# Database
MONGODB_URI=mongodb://localhost:27017/threadx
# or your MongoDB Atlas connection string
# JWT β use long random strings in production
JWT_ACCESS_SECRET=your_access_secret
JWT_REFRESH_SECRET=your_refresh_secret
# Optional overrides (defaults: 15m access, 7d refresh)
# JWT_ACCESS_TOKEN_EXPIRES_IN=15m
# JWT_REFRESH_TOKEN_EXPIRES_IN=7d
# Google OAuth
GOOGLE_CLIENT_ID=your_google_client_id
# Cloudinary
CLOUDINARY_CLOUD_NAME=your_cloud_name
CLOUDINARY_API_KEY=your_api_key
CLOUDINARY_API_SECRET=your_api_secret
# Arcjet (https://arcjet.com)
ARCJET_KEY=your_arcjet_key
# Email (Resend or Nodemailer SMTP)
RESEND_API_KEY=your_resend_api_keyStart the API:
npm run devThe server listens on http://localhost:5001 by default (GET /health returns a status JSON).
Production build:
npm run build
npm startOpen a second terminal:
cd frontend
npm installCreate frontend/.env:
VITE_THREADX_API_URL=http://localhost:5001/apiStart the dev server:
npm run devOpen http://localhost:5173. Make sure the backend is running first.
Production build:
npm run build
npm run preview| Path | Role |
|---|---|
frontend/ |
React 19 + Vite 7 SPA (PWA) |
backend/ |
Express 5 API, Socket.IO, MongoDB, Auth, Arcjet security |
There is no root-level package.json; install and run each package separately.
Implementation by Pappyjay23