A scalable, production-ready chat backend built with Node.js, Express, TypeScript, and MongoDB, designed using clean architecture principles and industry-standard messaging patterns.
This README documents all the work completed so far, the design decisions, and flow diagrams to clearly explain how the system works.
β JWT-based Authentication (protected routes)
β Conversations
- 1:1 (DM) conversations
- Group conversations
- Group admins support
β Messages
- Send messages
- Fetch messages with pagination
- Conversation-level lastMessage tracking
β Unread Message System (Industry Standard)
- Per-user read tracking
- Unread count per conversation
- Works for DM and Group chats
- Pagination-safe
Stores basic user details (name, avatar, etc.).
Represents a chat (DM or Group).
Key Fields:
type:dm | groupparticipants: User IDsadmins: User IDs (group only)lastMessage: Message ID
Represents a single chat message.
Key Fields:
conversationIdsendercontenttype(text / image / file)createdAt
Messages are immutable.
Tracks read state per user per conversation.
Meaning:
For a given user and conversation, what is the last message the user has read?
Key Fields:
conversationIduserIdlastReadMessageId
This model enables scalable unread message logic.
β Messages are NOT individually marked as read
β Each user maintains a read pointer (bookmark) per conversation
A message is considered read if:
message._id <= lastReadMessageId
Unread messages are derived, not stored.
User
β
βΌ
Auth Middleware
β
βΌ
sendMessageService
β
ββ Validate conversation
ββ Validate participant
β
βΌ
Create Message
β
βΌ
Update Conversation.lastMessage
β
βΌ
Return message
π Note:
- No unread state updated here
- Message writes stay fast
User opens chat
β
βΌ
GET /messages/:conversationId
β
βΌ
fetchMessagesService
β
ββ Validate conversation & participant
β
βΌ
Fetch messages (paginated)
β
βΌ
Find ConversationRead
β ββ If not exists β create
β
βΌ
Update lastReadMessageId (latest message)
β
βΌ
Return messages
π This is the only place where messages are marked as read.
User opens chat list
β
βΌ
GET /conversations
β
βΌ
getConversationsService
β
βΌ
Fetch all user conversations
β
βΌ
For each conversation:
β
ββ Get ConversationRead
ββ Build unread query
β ββ Same conversation
β ββ Sender != user
β ββ Message > lastReadMessageId
β
βΌ
Count unread messages
β
βΌ
Attach unreadCount
β
βΌ
Return conversation list
User
β
βΌ
Conversation
β
βΌ
Message
β²
β
ConversationRead
Conversationis sharedMessageis immutableConversationReadis user-specific
Unread Messages =
Messages where:
- conversationId matches
- sender != current user
- message._id > lastReadMessageId
If lastReadMessageId is null β all messages from others are unread.
A sends m1
B sends m2
B sends m3
A opens chat β lastRead = m3
B sends m4
Unread count for A:
1 (m4)
Unread count for B:
0
- REST APIs create data
- Socket.IO delivers events
- Clients never send messages via socket
- Server emits socket events after DB persistence
conversation:<conversationId>
Used for:
- Real-time messages
- Typing indicators (future)
- Read receipts (future)
Joined when:
- User opens a conversation
Left when:
- User switches conversation
Used for:
- Notifications
- Unread updates
- Presence (future)
Joined on:
- Socket connection
Send message β REST API
β DB save
β REST response
β Sender UI updates immediately
socket.on("message:new")
β append message to state
β UI updates instantly
- Pagination-safe unread logic
- No per-message read flags
- One DB write per chat open
- Works for DM & Group chats
- Real-time messaging using Socket.IO
- Real-time unread updates
- Aggregation-based unread optimization
- Read receipts ("Seen by X")
- Group admin permissions
This backend implements an industry-grade chat architecture similar to WhatsApp / Slack:
- Clean separation of concerns
- Scalable unread message design
- With real-time features
Frontend does not access refresh tokens directly.
- Socket.IO real-time messaging
- Redis-backed unread optimization
- Read receipts (βSeen byβ)
- Group permission enforcement
Zolo implements an industry-grade chat architecture with:
π¨βπ» Built as part of the Zolo Project