A real-time 1-to-1 messaging app built with the MERN stack and Socket.IO. Includes JWT-based authentication, live presence (online/last seen), and instant message delivery over WebSockets, with a REST API as the source of truth for message history.
- Authentication — username/password registration and login with hashed passwords (bcrypt) and JWT access tokens.
- Real-time messaging — messages sent over Socket.IO are delivered instantly to both sender and receiver; REST endpoints provide message history with cursor-based pagination.
- Presence — tracks online status and last-seen timestamps per user.
- User discovery — search for users to start a new conversation.
- Conversation model — deterministic 1-to-1 conversation pairing with a unique compound index, so the same two users always map to the same conversation thread.
- Security middleware — Helmet, CORS, request validation (Zod), and per-IP rate limiting on the API.
Backend
- Node.js, Express
- MongoDB with Mongoose
- Socket.IO (WebSocket transport, JWT-authenticated handshake)
- JWT (
jsonwebtoken) for stateless auth - Zod for request validation
- bcrypt for password hashing
- Helmet, CORS, express-rate-limit, Morgan
Frontend
- React 19 + Vite
- Axios for REST calls
- socket.io-client for real-time events
chatmon/
├── src/ # Backend source
│ ├── config/ # Env validation (Zod) and MongoDB connection
│ ├── controllers/ # HTTP request/response layer
│ ├── services/ # Business logic (auth, messages, users)
│ ├── models/ # Mongoose schemas: User, Conversation, Message
│ ├── routes/ # Express routers
│ ├── middleware/ # Auth guard, validation, error handling
│ ├── validation/ # Zod schemas for request bodies/queries
│ ├── sockets/ # Socket.IO server, JWT handshake auth, presence, event bus
│ └── server.js # App entrypoint
├── client/ # Frontend (React + Vite)
│ └── src/
│ ├── api/ # Axios client
│ ├── components/chat/ # Sidebar, ChatWindow, MessageInput, UserDiscovery
│ ├── context/ # AuthContext, SocketContext
│ ├── pages/ # AuthPage, ChatPage
│ └── utils/ # Message helpers
├── package.json # Backend dependencies and scripts
└── .env.example # Backend environment template
- Node.js 18+
- A running MongoDB instance (local or hosted, e.g. MongoDB Atlas)
git clone https://github.com/Vara715/chatmon.git
cd chatmonnpm install
cp .env.example .envFill in .env:
| Variable | Description | Default |
|---|---|---|
PORT |
Port the API server listens on | 4000 |
NODE_ENV |
development, test, or production |
development |
MONGODB_URI |
MongoDB connection string | mongodb://localhost:27017/chatmon |
JWT_ACCESS_SECRET |
Secret used to sign JWTs (use a long random string) | — |
JWT_ACCESS_EXPIRES_IN |
Access token lifetime (e.g. 15m, 8h, 7d) |
15m |
Run the API in dev mode (auto-restarts on changes):
npm run devThe API will be available at http://localhost:4000, with a health check at GET /health.
cd client
npm install
cp .env.example .envSet VITE_API_URL in client/.env to point at the backend origin (default http://localhost:4000).
npm run devThe client will be available at the Vite dev server URL printed in your terminal (typically http://localhost:5173).
All REST routes are mounted under /api and protected routes require an Authorization: Bearer <token> header.
| Method | Endpoint | Auth required | Description |
|---|---|---|---|
| POST | /api/auth/register |
No | Create a new user account |
| POST | /api/auth/login |
No | Authenticate and receive a JWT |
| GET | /api/users/me |
Yes | Get the current authenticated user |
| GET | /api/users/search |
Yes | Search users (for starting a conversation) |
| GET | /api/messages/history |
Yes | Get paginated message history with a user |
| POST | /api/messages/send |
Yes | Send a message (REST fallback) |
The Socket.IO handshake expects the JWT in auth.token (or an Authorization: Bearer <token> header).
| Event | Direction | Payload | Description |
|---|---|---|---|
message:send |
client → server | { toUserId, body } |
Send a message over the socket connection |
message:new |
server → client | Message object | Emitted to both sender and receiver |
presence:self |
server → client | { userId, online } |
Sent on connect to confirm presence state |
- Conversation uniqueness: each 1-to-1 conversation is keyed by a sorted
(participantLow, participantHigh)pair with a unique compound index, avoiding duplicate threads between the same two users. - Presence: tracked in-memory per server instance (
src/sockets/presence.js). For horizontal scaling across multiple server instances, this should be moved to Redis along with the Socket.IO Redis adapter. - Validation: all request bodies/queries are validated with Zod schemas before reaching controllers.
Backend (/)
npm run dev # Start with nodemon (auto-restart)
npm start # Start in production modeFrontend (/client)
npm run dev # Start Vite dev server
npm run build # Production build
npm run preview # Preview the production build
npm run lint # Run ESLint- Group conversations
- Message read receipts in the UI
- Media/file attachments
- Typing indicators
- Redis-backed presence for multi-instance deployments
This project is provided as-is for learning and portfolio purposes. Add a LICENSE file (e.g. MIT) if you intend to open-source it formally.