Message Controller
Controller: message.controller.js
Module: Conversation-v2
Purpose: Handles real-time message operations including sending, querying, editing, deleting, reactions, favorites, and reminders
Overview
The Message controller manages all message-related operations in the chat system. It supports rich messaging features including message editing, soft/hard deletion, favorites, reminders, reactions, and bidirectional pagination for chat history.
Methods
1. Send Message (sendMessage)
Sends a new message to a chat room.
Endpoint: POST /api/conversation-v2/message
Authentication: Required (JWT + conversation context)
Request Body:
{
"room_id": "507f1f77bcf86cd799439011",
"message": {
"type": "text",
"content": "Hello, how can I help you today?",
"attachments": []
},
"qid": "client_generated_uuid_123"
}
Response:
{
"_id": "507f1f77bcf86cd799439020",
"room_id": "507f1f77bcf86cd799439011",
"conversation_id": "507f1f77bcf86cd799439012",
"content": "Hello, how can I help you today?",
"type": "text",
"attachments": [],
"created_at": "2025-10-08T10:30:00.000Z",
"updated_at": "2025-10-08T10:30:00.000Z",
"deleted": false,
"deleted_for": [],
"favorite": [],
"reminders": [],
"reactions": [],
"qid": "client_generated_uuid_123"
}
MongoDB Operations:
| Collection | Operation | Query | Purpose |
|---|---|---|---|
messages | insertOne | New message | Create message |
rooms | updateOne | Update last message | Update room metadata |
Business Logic:
- Message is scoped to conversation and room
qid(queue ID) enables client-side optimistic updates- Service layer handles Socket.IO broadcast to room members
- Updates room's
last_messageandlast_activityfields
Message Flow:
graph TD
A[Client Sends Message] --> B[Validate Room Access]
B -->|Authorized| C[Create Message Document]
C --> D[Update Room Metadata]
D --> E[Broadcast via Socket.IO]
E --> F[Return Message + QID]
B -->|Unauthorized| G[Error: Access Denied]
Use Cases:
- Sending text messages in team chat
- Sending attachments (files, images)
- System messages (e.g., "User joined room")
- Bot responses
2. Query Messages (queryMessages)
Retrieves messages from a room with pagination and filtering support.
Endpoint: GET /api/conversation-v2/message
Authentication: Required (JWT + conversation context)
Query Parameters:
{
room_id: string; // Required: Room to fetch messages from
upperRef?: string; // Message ID for pagination (older messages)
lowerRef?: string; // Message ID for pagination (newer messages)
filter?: object; // MongoDB filter criteria
limit?: number; // Max messages to return (default: 20)
sort?: string; // Sort order ('asc' | 'desc')
search?: string; // Search query
get_unread?: boolean; // Return unread count
populate_room?: boolean; // Include room details
}
Request:
GET /api/conversation-v2/message?room_id=507f1f77bcf86cd799439011&limit=50&get_unread=true
Authorization: Bearer <token>
Response:
{
"success": true,
"messages": [
{
"_id": "507f1f77bcf86cd799439020",
"room_id": "507f1f77bcf86cd799439011",
"conversation_id": "507f1f77bcf86cd799439012",
"content": "Hello, how can I help you today?",
"type": "text",
"created_at": "2025-10-08T10:30:00.000Z",
"deleted": false,
"favorite": false,
"reminders": null,
"reactions": []
}
],
"unread_count": 3,
"room": {
"_id": "507f1f77bcf86cd799439011",
"name": "Support Chat",
"type": "direct"
},
"hasMore": true,
"upperRef": "507f1f77bcf86cd799439020",
"lowerRef": "507f1f77bcf86cd799439030"
}
MongoDB Operations:
| Collection | Operation | Query | Purpose |
|---|---|---|---|
rooms | findOne | { _id: room_id } | Validate room access |
messages | find | Complex pagination query | Get messages |
messages | countDocuments | Unread message filter | Count unread messages |
Business Logic:
- Room Access Validation: Verifies user is member or all_member of room
- Bidirectional Pagination:
upperRef- Get messages older than this IDlowerRef- Get messages newer than this ID
- Unread Tracking: Compares
last_message_readfrom room with message IDs - User-specific Filtering:
favoriteandremindersfiltered to current conversation- Shows if current user favorited/set reminder on each message
Pagination Strategy:
graph LR
A[Initial Load] --> B[Get Latest 20 Messages]
B --> C{User Scrolls}
C -->|Scroll Up| D[upperRef: Get Older]
C -->|Scroll Down| E[lowerRef: Get Newer]
D --> F[Append to Top]
E --> G[Append to Bottom]
Error Responses:
{
"error": "Cannot access messages of this room",
"statusCode": 400
}
Use Cases:
- Loading chat history on room open
- Infinite scroll pagination
- Searching message history
- Displaying unread message count
3. Update Message (updateMessage)
Edits the content of an existing message (only by sender).
Endpoint: PATCH /api/conversation-v2/message/:messageId
Authentication: Required (JWT + conversation context)
Request Body:
{
"content": "Updated message content"
}
Response:
{
"success": true,
"message": "Updated successfully"
}
MongoDB Operations:
| Collection | Operation | Query/Update | Purpose |
|---|---|---|---|
messages | findOne | { _id: messageId } | Get message |
messages | updateOne | $set: { content } | Update content |
Business Logic:
- Only message sender can edit (validated via
conversation_id) - Updates
contentfield only - Typically triggers "edited" indicator in UI via Socket.IO
- Does not update timestamp (preserves original
created_at)
Authorization Flow:
graph TD
A[Edit Request] --> B{Get Message}
B --> C{Sender Match?}
C -->|Yes| D[Update Content]
C -->|No| E[Error: Not Allowed]
B --> F[Error: Not Found]
D --> G[Success Response]
Error Responses:
// Not message owner
{
"error": "Cannot edit this message",
"statusCode": 403
}
// Message not found
{
"error": "Message does not exist",
"statusCode": 404
}
Use Cases:
- Fixing typos
- Updating message content
- Clarifying previous statements
4. Delete Message (Soft Delete) (deleteMessage)
Soft deletes a message for the current user only.
Endpoint: DELETE /api/conversation-v2/message/:messageId
Authentication: Required (JWT + conversation context)
Response:
{
"success": true,
"message": "Deleted successfully"
}
MongoDB Operations:
| Collection | Operation | Query/Update | Purpose |
|---|---|---|---|
messages | findOne | { _id: messageId } | Get message |
messages | updateOne | $push: { deleted_for: conversation_id } | Soft delete |
Business Logic:
- Adds current user's
conversation_idtodeleted_forarray - Message remains visible to other participants
- Used for "Delete for Me" functionality
- Any user can delete messages for themselves
Soft Delete Behavior:
| User | Action | Result |
|---|---|---|
| User A | Deletes message | Message hidden for User A only |
| User B | Views room | Message still visible |
| User A | Views room | Message not shown |
Use Cases:
- "Delete for Me" functionality
- Hiding unwanted messages from personal view
- Cleaning up personal chat history
5. Delete Message (Hard Delete) (deleteMessageAll)
Permanently deletes a message for all users (only by sender).
Endpoint: DELETE /api/conversation-v2/message/:messageId/all
Authentication: Required (JWT + conversation context)
Response:
{
"success": true,
"message": "Deleted successfully"
}
MongoDB Operations:
| Collection | Operation | Query/Update | Purpose |
|---|---|---|---|
messages | findOne | { _id: messageId } | Get message |
messages | updateOne | $set: { deleted: true } | Hard delete flag |
Business Logic:
- Only message sender can hard delete
- Sets
deleted: trueflag on message document - Message becomes unavailable to all room participants
- Used for "Delete for Everyone" functionality
Authorization:
if (message.conversation_id.toString() == conversation_id.toString()) {
// Authorized: User is message sender
await deleteMessageAll(messageId);
} else {
throw notAllowed('Cannot delete this message');
}
Hard Delete vs Soft Delete:
graph TD
A[Delete Request] --> B{Sender or Not?}
B -->|Sender| C[Hard Delete Option]
B -->|Not Sender| D[Soft Delete Only]
C --> E{Choice}
E -->|Delete for Everyone| F[Set deleted: true]
E -->|Delete for Me| G[Add to deleted_for]
D --> G
F --> H[Hidden for All Users]
G --> I[Hidden for User Only]
Error Responses:
{
"error": "Cannot delete this message",
"statusCode": 403
}
Use Cases:
- "Delete for Everyone" functionality
- Removing sensitive information
- Correcting major errors
6. Toggle Favorite (favorite)
Adds or removes a message from the current user's favorites.
Endpoint: POST /api/conversation-v2/message/:messageId/favorite
Authentication: Required (JWT + conversation context)
Response:
{
"success": true,
"message": {
"_id": "507f1f77bcf86cd799439020",
"favorite": true,
"content": "Important message",
"created_at": "2025-10-08T10:30:00.000Z"
}
}
MongoDB Operations:
| Collection | Operation | Query/Update | Purpose |
|---|---|---|---|
messages | updateOne | Toggle favorite entry | Add/remove favorite |
Business Logic:
- Toggles favorite status for current conversation
- Each message has array of
favoriteentries (one per user) - Service layer handles add/remove logic:
// If already favorited → Remove from array
// If not favorited → Add to array
Favorite Data Structure:
// In message document
favorite: [
{ conversation_id: ObjectId, timestamp: number },
{ conversation_id: ObjectId, timestamp: number },
];
Use Cases:
- Bookmarking important messages
- Creating personal reference list
- Quick access to key information
7. Get Reminders (getReminders)
Retrieves all message reminders for the current user.
Endpoint: GET /api/conversation-v2/message/reminders
Authentication: Required (JWT + conversation context)
Query Parameters:
{
page?: number; // Page number (default: 1)
limit?: number; // Items per page (default: 20)
room_id?: string; // Filter by specific room
}
Request:
GET /api/conversation-v2/message/reminders?page=1&limit=10
Authorization: Bearer <token>
Response:
{
"success": true,
"data": [
{
"_id": "507f1f77bcf86cd799439020",
"room_id": "507f1f77bcf86cd799439011",
"content": "Follow up on client inquiry",
"reminder": {
"time": 1633110000000,
"note": "Check with client about proposal"
},
"created_at": "2025-10-08T10:30:00.000Z"
}
],
"pagination": {
"page": 1,
"limit": 10,
"total": 25,
"pages": 3
}
}
MongoDB Operations:
| Collection | Operation | Query | Purpose |
|---|---|---|---|
messages | find | { "reminders.conversation_id": conversation_id } | Get user's reminders |
Business Logic:
- Returns messages with active reminders for current user
- Supports pagination for large reminder lists
- Can filter by room to see room-specific reminders
- Sorted by reminder time (earliest first)
Use Cases:
- Viewing all upcoming reminders
- Managing follow-up tasks
- To-do list from messages
8. Update Reminder (updateReminder)
Sets or updates a reminder on a message.
Endpoint: PATCH /api/conversation-v2/message/:messageId/reminder
Authentication: Required (JWT + conversation context)
Request Body:
{
"reminder": {
"time": 1633110000000,
"note": "Follow up with client about pricing"
}
}
Response:
{
"success": true,
"message": {
"_id": "507f1f77bcf86cd799439020",
"content": "Client asked about enterprise pricing",
"reminder": {
"time": 1633110000000,
"note": "Follow up with client about pricing"
}
}
}
MongoDB Operations:
| Collection | Operation | Query/Update | Purpose |
|---|---|---|---|
messages | updateOne | Update reminder entry | Set/update reminder |
Business Logic:
- Each user can have one reminder per message
- Reminder object contains:
time: Unix timestamp for reminder triggernote: Optional note for the reminder
- Updates existing reminder if already set
Reminder Data Structure:
// In message document
reminders: [
{
conversation_id: ObjectId,
time: number,
note: string,
},
];
Reminder Workflow:
graph TD
A[Set Reminder] --> B[Store in Message]
B --> C[Background Job Monitors]
C --> D{Time Reached?}
D -->|Yes| E[Trigger Notification]
D -->|No| C
E --> F[Socket.IO Event]
E --> G[Email/SMS Optional]
Use Cases:
- Setting follow-up reminders
- Scheduling message actions
- Task management from conversations
9. Delete Reminder (deleteReminder)
Removes a reminder from a message for the current user.
Endpoint: DELETE /api/conversation-v2/message/:messageId/reminder
Authentication: Required (JWT + conversation context)
Response:
{
"success": true,
"message": {
"_id": "507f1f77bcf86cd799439020",
"content": "Client asked about enterprise pricing",
"reminder": null
}
}
MongoDB Operations:
| Collection | Operation | Query/Update | Purpose |
|---|---|---|---|
messages | updateOne | $pull reminder entry | Remove reminder |
Business Logic:
- Removes reminder entry for current conversation
- Other users' reminders on same message remain intact
- Message remains favorited if it was
Use Cases:
- Completing reminder tasks
- Canceling scheduled follow-ups
- Cleaning up reminder list
10. Update Reaction (updateReaction)
Adds or updates an emoji reaction to a message.
Endpoint: PATCH /api/conversation-v2/message/:messageId/reaction
Authentication: Required (JWT + conversation context)
Request Body:
{
"reaction": "👍"
}
Response:
{
"success": true,
"message": {
"_id": "507f1f77bcf86cd799439020",
"content": "Great idea!",
"reactions": [
{
"conversation_id": "507f1f77bcf86cd799439012",
"emoji": "👍",
"timestamp": 1633024800000
}
]
}
}
MongoDB Operations:
| Collection | Operation | Query/Update | Purpose |
|---|---|---|---|
messages | updateOne | Update reaction entry | Add/update reaction |
Business Logic:
- Each user can have one reaction per message
- Updates existing reaction if user already reacted
- Supports emoji reactions (👍, ❤️, 😄, etc.)
- Broadcasts reaction update via Socket.IO
Reaction Data Structure:
// In message document
reactions: [
{
conversation_id: ObjectId,
emoji: string,
timestamp: number,
},
];
Reaction Aggregation:
graph LR
A[Message with Reactions] --> B{Group by Emoji}
B --> C[👍 x 5]
B --> D[❤️ x 3]
B --> E[😄 x 2]
C --> F[Display Summary]
D --> F
E --> F
Use Cases:
- Quick acknowledgment of messages
- Non-verbal feedback
- Sentiment expression
- Poll-like functionality
11. Delete Reaction (deleteReaction)
Removes the current user's reaction from a message.
Endpoint: DELETE /api/conversation-v2/message/:messageId/reaction
Authentication: Required (JWT + conversation context)
Response:
{
"success": true,
"message": {
"_id": "507f1f77bcf86cd799439020",
"content": "Great idea!",
"reactions": []
}
}
MongoDB Operations:
| Collection | Operation | Query/Update | Purpose |
|---|---|---|---|
messages | updateOne | $pull reaction entry | Remove reaction |
Business Logic:
- Removes only current user's reaction
- Other users' reactions remain
- No error if user hasn't reacted
Use Cases:
- Undoing reactions
- Changing reaction (delete then add new)
- Removing accidental reactions
Data Models
Message Schema
{
_id: ObjectId;
room_id: ObjectId; // Room this message belongs to
conversation_id: ObjectId; // Sender's conversation ID
// Content
content: string; // Message text
type: string; // 'text' | 'image' | 'file' | 'system'
attachments: Array<{
url: string;
type: string;
size: number;
name: string;
}>;
// Deletion Tracking
deleted: boolean; // Hard delete flag (for everyone)
deleted_for: ObjectId[]; // Soft delete (per-user)
// User-specific Features
favorite: Array<{
conversation_id: ObjectId;
timestamp: number;
}>;
reminders: Array<{
conversation_id: ObjectId;
time: number;
note: string;
}>;
reactions: Array<{
conversation_id: ObjectId;
emoji: string;
timestamp: number;
}>;
// Timestamps
created_at: Date;
updated_at: Date;
// Client Tracking
qid?: string; // Client-generated queue ID
}
Message Types
| Type | Description | Example |
|---|---|---|
text | Plain text message | "Hello world" |
image | Image attachment | Photo, screenshot |
file | File attachment | PDF, document |
system | System notification | "User joined room" |
note | Internal note (support) | Agent notes |
Socket.IO Integration
Real-time Events
Messages trigger Socket.IO events for real-time updates:
// New message event
socket.to(roomId).emit('message:new', {
message: messageObject,
room_id: roomId,
});
// Message updated event
socket.to(roomId).emit('message:updated', {
message_id: messageId,
content: newContent,
});
// Message deleted event
socket.to(roomId).emit('message:deleted', {
message_id: messageId,
deleted_for_all: boolean,
});
// Reaction event
socket.to(roomId).emit('message:reaction', {
message_id: messageId,
reaction: { emoji, conversation_id },
});
Pagination Deep Dive
Bidirectional Pagination Strategy
// Load initial messages
GET /message?room_id=123&limit=20
→ Returns latest 20 messages + upperRef + lowerRef
// User scrolls up (load older)
GET /message?room_id=123&upperRef=<id>&limit=20
→ Returns 20 messages older than upperRef
// User scrolls down (load newer)
GET /message?room_id=123&lowerRef=<id>&limit=20
→ Returns 20 messages newer than lowerRef
Unread Message Tracking
graph TD
A[User Opens Room] --> B[Get last_message_read from Room]
B --> C[Count Messages After last_message_read]
C --> D[Display Unread Count Badge]
D --> E[User Reads Messages]
E --> F[Update last_message_read in Room]
Performance Considerations
Indexing Requirements
// messages collection
db.messages.createIndex({ room_id: 1, created_at: -1 });
db.messages.createIndex({ 'reminders.conversation_id': 1, 'reminders.time': 1 });
db.messages.createIndex({ 'favorite.conversation_id': 1 });
Query Optimization
- Pagination: Use indexed
created_atfield for efficient pagination - Room Filtering: Always filter by
room_idfirst - Projection: Only return needed fields in large message lists
- Deleted Messages: Filter
deleted: falseanddeleted_forin query
Error Handling
Common Errors
| Status Code | Error | Cause |
|---|---|---|
| 400 | Cannot access messages of this room | User not room member |
| 403 | Cannot edit this message | Not message sender |
| 403 | Cannot delete this message | Not message sender (hard delete) |
| 404 | Message does not exist | Invalid message ID |
| 401 | Unauthorized | Missing/invalid JWT |
Testing Recommendations
Unit Tests
describe('Message Controller', () => {
describe('sendMessage', () => {
it('should create message with qid', async () => {});
it('should validate room access', async () => {});
});
describe('queryMessages', () => {
it('should return paginated messages', async () => {});
it('should filter deleted_for messages', async () => {});
it('should calculate unread count', async () => {});
});
describe('favorite', () => {
it('should toggle favorite status', async () => {});
it('should support multiple users favoriting', async () => {});
});
});
Last Updated: October 8, 2025
Documented By: AI Documentation System
Source: internal/api/v1/conversation-v2/controllers/message.controller.js