Overview
Messages are individual chat messages within a conversation. The Horse Trust platform supports both REST API and Socket.io for sending and receiving messages in real-time.
Authentication
All message endpoints require authentication via JWT token.
REST API:
Authorization: Bearer <your_jwt_token>
Socket.io:
const socket = io ( 'https://api.horsetrust.com' , {
auth: {
token: '<your_jwt_token>'
}
});
Get Messages
curl https://api.horsetrust.com/api/chat/conversations/65a1b2c3d4e5f6a7b8c9d0e1/messages?page= 1 & limit = 30 \
-H "Authorization: Bearer <token>"
Success Response
Error Response
{
"success" : true ,
"data" : [
{
"_id" : "65a1b2c3d4e5f6a7b8c9d0e3" ,
"conversation_id" : "65a1b2c3d4e5f6a7b8c9d0e1" ,
"sender_id" : {
"_id" : "507f1f77bcf86cd799439010" ,
"full_name" : "John Smith" ,
"profile_picture_url" : "https://cdn.horsetrust.com/users/john.jpg"
},
"text" : "Is this horse still available?" ,
"is_read" : true ,
"read_at" : "2024-03-15T10:32:00.000Z" ,
"sent_at" : "2024-03-15T10:30:00.000Z"
},
{
"_id" : "65a1b2c3d4e5f6a7b8c9d0e4" ,
"conversation_id" : "65a1b2c3d4e5f6a7b8c9d0e1" ,
"sender_id" : {
"_id" : "507f1f77bcf86cd799439011" ,
"full_name" : "Jane Doe" ,
"profile_picture_url" : "https://cdn.horsetrust.com/users/jane.jpg"
},
"text" : "Yes, Thunder is still available! Would you like to schedule a visit?" ,
"is_read" : true ,
"read_at" : "2024-03-15T10:36:00.000Z" ,
"sent_at" : "2024-03-15T10:35:00.000Z"
}
]
}
GET /api/chat/conversations/:id/messages
Retrieve paginated message history for a conversation. Messages are returned in chronological order (oldest first). When you fetch messages, all unread messages from other users are automatically marked as read.
The conversation ID to retrieve messages from
Page number for pagination (minimum: 1)
Number of messages per page (maximum: 100)
Response Fields
Indicates if the request was successful
Array of message objects in chronological order (oldest first) Unique message identifier
ID of the conversation this message belongs to
Populated sender user object URL to the user’s profile picture
Message content (maximum 2000 characters)
Whether the message has been read by the recipient
ISO 8601 timestamp when the message was read (null if unread)
ISO 8601 timestamp when the message was sent
Error message (only present when success is false)
Fetching messages automatically marks unread messages from other participants as read and sets the read_at timestamp.
Error Responses
404 Not Found : Conversation not found or user is not a participant
500 Internal Server Error : Server error occurred
Send Message (REST)
curl -X POST https://api.horsetrust.com/api/chat/conversations/65a1b2c3d4e5f6a7b8c9d0e1/messages \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"text": "Yes, I would love to schedule a visit!"
}'
Success Response
Error Response
{
"success" : true ,
"data" : {
"_id" : "65a1b2c3d4e5f6a7b8c9d0e5" ,
"conversation_id" : "65a1b2c3d4e5f6a7b8c9d0e1" ,
"sender_id" : "507f1f77bcf86cd799439010" ,
"text" : "Yes, I would love to schedule a visit!" ,
"is_read" : false ,
"sent_at" : "2024-03-15T10:40:00.000Z"
}
}
POST /api/chat/conversations/:id/messages
Send a message to a conversation via REST API. This endpoint serves as a fallback when Socket.io is not available. The conversation’s last_message field is automatically updated.
The conversation ID to send the message to
The message content (maximum 2000 characters)
Response Fields
Indicates if the request was successful
The created message object Unique message identifier
ObjectId of the authenticated user who sent the message
Always false for newly created messages
ISO 8601 timestamp when the message was sent
Error message (only present when success is false)
Error Responses
404 Not Found : Conversation not found or user is not a participant
500 Internal Server Error : Server error occurred
Socket.io Real-Time Messaging
Connection Setup
import { io } from 'socket.io-client' ;
const socket = io ( 'https://api.horsetrust.com' , {
auth: {
token: yourJwtToken
}
});
socket . on ( 'connect' , () => {
console . log ( 'Connected to Socket.io' );
});
socket . on ( 'connect_error' , ( error ) => {
console . error ( 'Connection error:' , error . message );
});
Socket.io authentication uses JWT tokens passed in the auth.token field during connection. Invalid or missing tokens will result in a connection error.
Join Conversation Room
Before receiving real-time messages, join the conversation room:
const conversationId = '65a1b2c3d4e5f6a7b8c9d0e1' ;
socket . emit ( 'join_conversation' , conversationId );
The ID of the conversation to join
Send Message
Send a message via Socket.io with acknowledgment:
const messageData = {
conversation_id: '65a1b2c3d4e5f6a7b8c9d0e1' ,
text: 'Hello, is this horse still available?'
};
socket . emit ( 'send_message' , messageData , ( response ) => {
if ( response . success ) {
console . log ( 'Message sent:' , response . data );
} else {
console . error ( 'Error:' , response . message );
}
});
Event: send_message
The ID of the conversation to send the message to
The message content (maximum 2000 characters)
Acknowledgment Response:
{
"success" : true ,
"data" : {
"_id" : "65a1b2c3d4e5f6a7b8c9d0e5" ,
"conversation_id" : "65a1b2c3d4e5f6a7b8c9d0e1" ,
"sender_id" : {
"_id" : "507f1f77bcf86cd799439010" ,
"full_name" : "John Smith" ,
"profile_picture_url" : "https://cdn.horsetrust.com/users/john.jpg"
},
"text" : "Hello, is this horse still available?" ,
"is_read" : false ,
"sent_at" : "2024-03-15T10:40:00.000Z"
}
}
Receive New Messages
Listen for new messages in conversations you’ve joined:
socket . on ( 'new_message' , ( message ) => {
console . log ( 'New message received:' , message );
// Update your UI with the new message
});
Event Payload:
{
"_id" : "65a1b2c3d4e5f6a7b8c9d0e5" ,
"conversation_id" : "65a1b2c3d4e5f6a7b8c9d0e1" ,
"sender_id" : {
"_id" : "507f1f77bcf86cd799439011" ,
"full_name" : "Jane Doe" ,
"profile_picture_url" : "https://cdn.horsetrust.com/users/jane.jpg"
},
"text" : "Yes, it is! Would you like to schedule a visit?" ,
"is_read" : false ,
"sent_at" : "2024-03-15T10:45:00.000Z"
}
The new_message event is broadcast to all users in the conversation room, including the sender.
Message Notifications
Receive notifications when you receive a new message (delivered to your personal user room):
socket . on ( 'message_notification' , ( notification ) => {
console . log ( 'New message notification:' , notification );
// Show a notification badge or alert
});
Event Payload:
{
"conversation_id" : "65a1b2c3d4e5f6a7b8c9d0e1" ,
"sender" : "507f1f77bcf86cd799439011" ,
"preview" : "Yes, it is! Would you like to schedule a visit?"
}
ID of the conversation where the message was sent
ObjectId of the user who sent the message
First 60 characters of the message text
Typing Indicators
Send and receive typing indicators:
// Send typing indicator
const conversationId = '65a1b2c3d4e5f6a7b8c9d0e1' ;
socket . emit ( 'typing' , conversationId );
// Stop typing indicator
socket . emit ( 'stop_typing' , conversationId );
// Receive typing indicators from others
socket . on ( 'user_typing' , ( data ) => {
console . log ( `User ${ data . userId } is typing...` );
});
socket . on ( 'user_stop_typing' , ( data ) => {
console . log ( `User ${ data . userId } stopped typing` );
});
Typing Event Payloads:
{
"userId" : "507f1f77bcf86cd799439011"
}
Data Model
Message Schema
{
conversation_id : ObjectId , // Required, indexed
sender_id : ObjectId , // Required
text : String , // Required, max 2000 chars
is_read : Boolean , // Default: false
read_at : Date , // Null until read
deleted_at : Date , // Soft delete timestamp
sent_at : Date // Auto-generated timestamp
}
Validation Rules
text field is required and limited to 2000 characters
conversation_id must reference a valid conversation
sender_id must be a participant in the conversation
Messages are soft-deleted (using deleted_at) and excluded from queries
Indexes
conversation_id: Used for efficiently querying messages in a conversation
Automatic Behavior
When messages are fetched via GET endpoint, unread messages from other users are automatically marked as read
When a message is sent, the conversation’s last_message field is updated
The sent_at timestamp is automatically set on creation
Messages with deleted_at set are excluded from all queries
Complete Example
Real-time Chat Implementation
import { io } from 'socket.io-client' ;
// Initialize Socket.io connection
const socket = io ( 'https://api.horsetrust.com' , {
auth: { token: localStorage . getItem ( 'jwt_token' ) }
});
// Handle connection
socket . on ( 'connect' , () => {
console . log ( 'Connected to chat' );
// Join conversation
const conversationId = '65a1b2c3d4e5f6a7b8c9d0e1' ;
socket . emit ( 'join_conversation' , conversationId );
// Load message history via REST API
loadMessageHistory ( conversationId );
});
// Listen for new messages
socket . on ( 'new_message' , ( message ) => {
displayMessage ( message );
});
// Listen for notifications
socket . on ( 'message_notification' , ( notification ) => {
updateUnreadBadge ();
showNotification ( notification . preview );
});
// Send message
function sendMessage ( conversationId , text ) {
socket . emit ( 'send_message' ,
{ conversation_id: conversationId , text },
( response ) => {
if ( ! response . success ) {
console . error ( 'Failed to send:' , response . message );
// Fallback to REST API
sendMessageViaREST ( conversationId , text );
}
}
);
}
// Typing indicators
let typingTimeout ;
function handleTyping ( conversationId ) {
socket . emit ( 'typing' , conversationId );
clearTimeout ( typingTimeout );
typingTimeout = setTimeout (() => {
socket . emit ( 'stop_typing' , conversationId );
}, 1000 );
}
// Load history via REST
async function loadMessageHistory ( conversationId ) {
const response = await fetch (
`https://api.horsetrust.com/api/chat/conversations/ ${ conversationId } /messages?page=1&limit=50` ,
{
headers: {
'Authorization' : `Bearer ${ localStorage . getItem ( 'jwt_token' ) } `
}
}
);
const data = await response . json ();
if ( data . success ) {
data . data . forEach ( displayMessage );
}
}
// Fallback to REST API
async function sendMessageViaREST ( conversationId , text ) {
const response = await fetch (
`https://api.horsetrust.com/api/chat/conversations/ ${ conversationId } /messages` ,
{
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ localStorage . getItem ( 'jwt_token' ) } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({ text })
}
);
const data = await response . json ();
if ( data . success ) {
displayMessage ( data . data );
}
}