Skip to main content

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:

CollectionOperationQueryPurpose
messagesinsertOneNew messageCreate message
roomsupdateOneUpdate last messageUpdate 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_message and last_activity fields

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:

CollectionOperationQueryPurpose
roomsfindOne{ _id: room_id }Validate room access
messagesfindComplex pagination queryGet messages
messagescountDocumentsUnread message filterCount unread messages

Business Logic:

  • Room Access Validation: Verifies user is member or all_member of room
  • Bidirectional Pagination:
    • upperRef - Get messages older than this ID
    • lowerRef - Get messages newer than this ID
  • Unread Tracking: Compares last_message_read from room with message IDs
  • User-specific Filtering:
    • favorite and reminders filtered 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:

CollectionOperationQuery/UpdatePurpose
messagesfindOne{ _id: messageId }Get message
messagesupdateOne$set: { content }Update content

Business Logic:

  • Only message sender can edit (validated via conversation_id)
  • Updates content field 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:

CollectionOperationQuery/UpdatePurpose
messagesfindOne{ _id: messageId }Get message
messagesupdateOne$push: { deleted_for: conversation_id }Soft delete

Business Logic:

  • Adds current user's conversation_id to deleted_for array
  • Message remains visible to other participants
  • Used for "Delete for Me" functionality
  • Any user can delete messages for themselves

Soft Delete Behavior:

UserActionResult
User ADeletes messageMessage hidden for User A only
User BViews roomMessage still visible
User AViews roomMessage 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:

CollectionOperationQuery/UpdatePurpose
messagesfindOne{ _id: messageId }Get message
messagesupdateOne$set: { deleted: true }Hard delete flag

Business Logic:

  • Only message sender can hard delete
  • Sets deleted: true flag 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:

CollectionOperationQuery/UpdatePurpose
messagesupdateOneToggle favorite entryAdd/remove favorite

Business Logic:

  • Toggles favorite status for current conversation
  • Each message has array of favorite entries (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:

CollectionOperationQueryPurpose
messagesfind{ "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:

CollectionOperationQuery/UpdatePurpose
messagesupdateOneUpdate reminder entrySet/update reminder

Business Logic:

  • Each user can have one reminder per message
  • Reminder object contains:
    • time: Unix timestamp for reminder trigger
    • note: 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:

CollectionOperationQuery/UpdatePurpose
messagesupdateOne$pull reminder entryRemove 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:

CollectionOperationQuery/UpdatePurpose
messagesupdateOneUpdate reaction entryAdd/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:

CollectionOperationQuery/UpdatePurpose
messagesupdateOne$pull reaction entryRemove 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

TypeDescriptionExample
textPlain text message"Hello world"
imageImage attachmentPhoto, screenshot
fileFile attachmentPDF, document
systemSystem notification"User joined room"
noteInternal 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

  1. Pagination: Use indexed created_at field for efficient pagination
  2. Room Filtering: Always filter by room_id first
  3. Projection: Only return needed fields in large message lists
  4. Deleted Messages: Filter deleted: false and deleted_for in query

Error Handling

Common Errors

Status CodeErrorCause
400Cannot access messages of this roomUser not room member
403Cannot edit this messageNot message sender
403Cannot delete this messageNot message sender (hard delete)
404Message does not existInvalid message ID
401UnauthorizedMissing/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

💬

Documentation Assistant

Ask me anything about the docs

Hi! I'm your documentation assistant. Ask me anything about the docs!

I can help you with:
- Code examples
- Configuration details
- Troubleshooting
- Best practices

Try asking: How do I configure the API?
09:31 AM