Skip to main content

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": 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.
id
string
required
The conversation ID to retrieve messages from
page
string
default:"1"
Page number for pagination (minimum: 1)
limit
string
default:"30"
Number of messages per page (maximum: 100)

Response Fields

success
boolean
required
Indicates if the request was successful
data
array
Array of message objects in chronological order (oldest first)
_id
string
Unique message identifier
conversation_id
string
ID of the conversation this message belongs to
sender_id
object
Populated sender user object
_id
string
User’s unique identifier
full_name
string
User’s full name
profile_picture_url
string
URL to the user’s profile picture
text
string
Message content (maximum 2000 characters)
is_read
boolean
Whether the message has been read by the recipient
read_at
string
ISO 8601 timestamp when the message was read (null if unread)
sent_at
string
ISO 8601 timestamp when the message was sent
message
string
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": 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.
id
string
required
The conversation ID to send the message to
text
string
required
The message content (maximum 2000 characters)

Response Fields

success
boolean
required
Indicates if the request was successful
data
object
The created message object
_id
string
Unique message identifier
conversation_id
string
ID of the conversation
sender_id
string
ObjectId of the authenticated user who sent the message
text
string
Message content
is_read
boolean
Always false for newly created messages
sent_at
string
ISO 8601 timestamp when the message was sent
message
string
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);
conversationId
string
required
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
conversation_id
string
required
The ID of the conversation to send the message to
text
string
required
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?"
}
conversation_id
string
ID of the conversation where the message was sent
sender
string
ObjectId of the user who sent the message
preview
string
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);
  }
}

Build docs developers (and LLMs) love