Skip to main content

Overview

The unread count endpoint returns the total number of unread messages across all conversations for the authenticated user. This is useful for displaying notification badges in your application’s UI.

Authentication

This endpoint requires authentication via JWT token in the Authorization header.
Authorization: Bearer <your_jwt_token>

Get Unread Count

curl https://api.horsetrust.com/api/chat/unread-count \
  -H "Authorization: Bearer <token>"
{
  "success": true,
  "unread_count": 5
}

GET /api/chat/unread-count

Returns the total number of unread messages for the authenticated user across all their conversations.

Response Fields

success
boolean
required
Indicates if the request was successful
unread_count
number
required
Total number of unread messages across all conversations. Returns 0 if there are no unread messages.
message
string
Error message (only present when success is false)

How It Works

The endpoint:
  1. Finds all conversations where the authenticated user is a participant
  2. Counts all messages in those conversations where:
    • The sender is NOT the authenticated user
    • The is_read field is false
  3. Returns the total count

Error Responses

  • 401 Unauthorized: Missing or invalid authentication token
  • 500 Internal Server Error: Server error occurred

Usage Examples

Display Notification Badge

React
import { useEffect, useState } from 'react';

function NotificationBadge() {
  const [unreadCount, setUnreadCount] = useState(0);
  
  useEffect(() => {
    async function fetchUnreadCount() {
      const response = await fetch('https://api.horsetrust.com/api/chat/unread-count', {
        headers: {
          'Authorization': `Bearer ${localStorage.getItem('jwt_token')}`
        }
      });
      
      const data = await response.json();
      if (data.success) {
        setUnreadCount(data.unread_count);
      }
    }
    
    // Fetch on mount
    fetchUnreadCount();
    
    // Poll every 30 seconds
    const interval = setInterval(fetchUnreadCount, 30000);
    
    return () => clearInterval(interval);
  }, []);
  
  if (unreadCount === 0) return null;
  
  return (
    <div className="notification-badge">
      {unreadCount > 99 ? '99+' : unreadCount}
    </div>
  );
}

Real-time Updates with Socket.io

For better user experience, combine the unread count endpoint with Socket.io message notifications:
import { io } from 'socket.io-client';

let unreadCount = 0;

// Initialize Socket.io
const socket = io('https://api.horsetrust.com', {
  auth: { token: localStorage.getItem('jwt_token') }
});

// Get initial count on load
async function initializeUnreadCount() {
  const response = await fetch('https://api.horsetrust.com/api/chat/unread-count', {
    headers: {
      'Authorization': `Bearer ${localStorage.getItem('jwt_token')}`
    }
  });
  
  const data = await response.json();
  if (data.success) {
    unreadCount = data.unread_count;
    updateBadge(unreadCount);
  }
}

// Increment count on new message notification
socket.on('message_notification', (notification) => {
  unreadCount++;
  updateBadge(unreadCount);
  
  // Show system notification
  new Notification('New message', {
    body: notification.preview
  });
});

// Decrement count when user reads messages
function onConversationOpened(conversationId) {
  // When user opens a conversation and reads messages,
  // fetch updated unread count
  initializeUnreadCount();
}

function updateBadge(count) {
  const badge = document.getElementById('unread-badge');
  if (count > 0) {
    badge.textContent = count > 99 ? '99+' : count;
    badge.style.display = 'block';
  } else {
    badge.style.display = 'none';
  }
}

initializeUnreadCount();

Mobile App Integration

Swift
import Foundation

class ChatService {
    private let baseURL = "https://api.horsetrust.com"
    private var token: String
    
    init(token: String) {
        self.token = token
    }
    
    func getUnreadCount() async throws -> Int {
        let url = URL(string: "\(baseURL)/api/chat/unread-count")!
        var request = URLRequest(url: url)
        request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        
        let (data, _) = try await URLSession.shared.data(for: request)
        let response = try JSONDecoder().decode(UnreadCountResponse.self, from: data)
        
        if response.success {
            return response.unreadCount
        } else {
            throw ChatError.serverError(response.message ?? "Unknown error")
        }
    }
    
    func updateBadge() async {
        do {
            let count = try await getUnreadCount()
            await MainActor.run {
                UIApplication.shared.applicationIconBadgeNumber = count
            }
        } catch {
            print("Failed to update badge: \(error)")
        }
    }
}

struct UnreadCountResponse: Codable {
    let success: Bool
    let unreadCount: Int
    let message: String?
    
    enum CodingKeys: String, CodingKey {
        case success
        case unreadCount = "unread_count"
        case message
    }
}

enum ChatError: Error {
    case serverError(String)
}

Best Practices

Polling Strategy

The recommended polling interval depends on your use case:
  • Active chat view: Poll every 10-30 seconds
  • Background/other views: Poll every 1-2 minutes
  • Mobile apps: Poll when app comes to foreground
Always combine with Socket.io for real-time updates when possible to reduce unnecessary API calls.
To minimize server load and improve performance:
  1. Use Socket.io’s message_notification event to increment the count client-side
  2. Only fetch the actual count periodically or when the user opens the app
  3. Store the count in local state and update it optimistically
  4. Reset the count when users read messages (mark them as read)
If the unread count request fails:
  • Keep showing the last known count
  • Don’t show “0” as it may be misleading
  • Retry with exponential backoff
  • Show a visual indicator if the count is stale

Optimistic Updates

Update the count optimistically when users interact with messages:
// When user opens a conversation with N unread messages
function onConversationOpened(conversation) {
  const unreadInThisConversation = conversation.messages.filter(
    msg => !msg.is_read && msg.sender_id !== currentUserId
  ).length;
  
  // Optimistically decrease the count
  setUnreadCount(prev => Math.max(0, prev - unreadInThisConversation));
  
  // The actual read status is updated server-side when fetching messages
}

// When user receives a new message notification
socket.on('message_notification', () => {
  // Optimistically increase the count
  setUnreadCount(prev => prev + 1);
});

// Periodically sync with server to fix any drift
setInterval(async () => {
  const response = await fetch('/api/chat/unread-count', {
    headers: { 'Authorization': `Bearer ${token}` }
  });
  const data = await response.json();
  if (data.success) {
    setUnreadCount(data.unread_count); // Sync with server truth
  }
}, 60000); // Every minute


Implementation Checklist

1

Initial Load

Fetch the unread count when your application loads to display the initial badge state.
2

Socket.io Integration

Listen to message_notification events to increment the count in real-time when new messages arrive.
3

Optimistic Updates

Decrease the count optimistically when users open conversations and read messages.
4

Periodic Sync

Poll the endpoint periodically (every 1-2 minutes) to sync with the server and correct any client-side drift.
5

Error Handling

Handle network errors gracefully by keeping the last known count and retrying with exponential backoff.

Build docs developers (and LLMs) love