Messaging Feature
Real-time messaging system with SignalR (WebSocket) and REST. The backend uses api/Chat for conversations, messages, and groups; api/Call for calls; api/StrangerChat for stranger chat. Real-time delivery is handled by the ChatWorker (SignalR hub).
Overview
The messaging feature enables:
- Real-time one-on-one conversations
- Group conversations with multiple participants
- Media sharing (images, videos, files)
- Message status tracking (sent, delivered, read)
- Typing indicators
- Message search and history
- Web Push (browser) and Expo Push (mobile) for chat and call notifications when the tab or app is in the background — see Web Push and Expo Push
Starting a Conversation
Send a Text Message
Send a simple text message to start or continue a conversation:
POST /api/Chat/messages
Authorization: Bearer {token}
Content-Type: application/json
{
"recipientId": "user-guid",
"content": "Hello! How are you?",
"type": "text"
}
Response:
{
"success": true,
"data": {
"id": "message-456",
"conversationId": "conv-789",
"senderId": "current-user-id",
"recipientId": "user-123",
"content": "Hello! How are you?",
"type": "text",
"status": "sent",
"createdAt": "2024-01-01T12:00:00Z",
"readAt": null
}
}
Send Media Message
Share images, videos, or files via api/Chat/upload or api/Chat/upload-multiple:
POST /api/Chat/upload
Authorization: Bearer {token}
Content-Type: multipart/form-data
FormData:
- recipientId: "user-guid"
- conversationId: "conv-guid" // if existing
- file: file.jpg
Supported Media Types:
- Images: JPEG, PNG, GIF, WebP (max 10MB)
- Videos: MP4, WebM, MOV (max 100MB)
- Files: PDF, DOC, DOCX, XLS, XLSX, ZIP (max 50MB)
Response:
{
"success": true,
"data": {
"id": "message-456",
"type": "image",
"content": "Check this out!",
"media": {
"url": "https://cdn.example.com/messages/image-123.jpg",
"thumbnail": "https://cdn.example.com/messages/image-123-thumb.jpg",
"size": 1024000,
"mimeType": "image/jpeg"
},
"status": "sent",
"createdAt": "2024-01-01T12:00:00Z"
}
}
Viewing Conversations
Get All Conversations
Retrieve list of all conversations:
GET /api/Chat/conversations?skip=0&take=50&includeArchived=false
Authorization: Bearer {token}
Response:
{
"success": true,
"data": [
{
"id": "conv-123",
"type": "direct", // or "group"
"participants": [
{
"id": "user-1",
"fullName": "John Doe",
"username": "johndoe",
"avatar": "https://..."
},
{
"id": "user-2",
"fullName": "Jane Smith",
"username": "janesmith",
"avatar": "https://..."
}
],
"lastMessage": {
"id": "msg-456",
"content": "See you tomorrow!",
"senderId": "user-1",
"createdAt": "2024-01-01T11:00:00Z"
},
"unreadCount": 2,
"updatedAt": "2024-01-01T11:00:00Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 15,
"totalPages": 1
}
}
Get Messages in a Conversation
Retrieve messages from a specific conversation:
GET /api/Chat/messages?chatId={conversationId}&skip=0&take=50&before={isoDate}
Authorization: Bearer {token}
Query Parameters:
page- Page number (default: 1)limit- Messages per page (default: 50, max: 100)before- Get messages before this timestampafter- Get messages after this timestamp
Response:
{
"success": true,
"data": {
"conversationId": "conv-123",
"messages": [
{
"id": "msg-456",
"senderId": "user-1",
"content": "Hello!",
"type": "text",
"status": "read",
"createdAt": "2024-01-01T10:00:00Z",
"readAt": "2024-01-01T10:01:00Z"
},
{
"id": "msg-457",
"senderId": "user-2",
"content": "Hi there!",
"type": "text",
"status": "read",
"createdAt": "2024-01-01T10:00:30Z",
"readAt": "2024-01-01T10:00:35Z"
}
],
"pagination": {
"page": 1,
"limit": 50,
"hasMore": false
}
}
}
Real-time Features (SignalR)
The chat real-time layer uses SignalR (not raw WebSockets). The ChatWorker hosts the hub at /hubs/chat (and /hubs/chat-online for presence). The frontend uses @microsoft/signalr and connects to the URL configured by VITE_CHAT_HUB_URL (or fallback from API base).
Connecting to the Chat Hub
import * as signalR from '@microsoft/signalr';
const connection = new signalR.HubConnectionBuilder()
.withUrl(`${chatHubBaseUrl}/hubs/chat?userId=${userId}`, {
accessTokenFactory: () => getAccessToken()
})
.withAutomaticReconnect()
.build();
await connection.start();
Joining a Conversation and Receiving Messages
// Join a conversation to receive real-time messages
await connection.invoke('JoinConversation', conversationId);
// Listen for new messages
connection.on('ReceiveMessage', (message) => {
handleNewMessage(message); // message has messageId, content, senderId, etc.
});
// Leave when switching away
await connection.invoke('LeaveConversation', conversationId);
Sending Messages via SignalR
Messages can be sent via REST (POST /api/Chat/messages) or SignalR when connected. The frontend typically tries SignalR first and falls back to REST.
// Send text (SignalR)
await connection.invoke('SendMessage', {
recipientId: 'user-guid',
content: 'Hello!',
type: 'text'
});
// Typing indicator
await connection.invoke('SendTypingIndicator', conversationId, true); // start
await connection.invoke('SendTypingIndicator', conversationId, false); // stop
Message and Presence Events
| Event | Description | Payload |
|---|---|---|
ReceiveMessage | New message in conversation | Message object (messageId, content, senderId, type, etc.) |
MessageReaction | Reaction added/updated on a message | { messageId, userId, reaction } |
ConversationDeleted | Conversation removed | { conversationId } |
| Presence (chat-online hub) | User online/offline | Varies by hub method |
Read receipts and delivery status are supported via REST (POST /api/Chat/messages/{messageId}/read) and may be reflected in SignalR events depending on the backend implementation.
Group Conversations
Create Group
Create a group conversation with multiple participants:
POST /api/Chat/groups
Authorization: Bearer {token}
Content-Type: application/json
{
"name": "Project Team",
"description": "Team discussion for the project",
"memberIds": ["user-1", "user-2", "user-3"],
"avatar": "optional-avatar-url"
}
Response:
{
"success": true,
"data": {
"id": "group-123",
"conversationId": "conv-456",
"name": "Project Team",
"description": "Team discussion for the project",
"avatar": "https://...",
"adminId": "current-user-id",
"members": [
{
"id": "user-1",
"fullName": "John Doe",
"role": "admin" // or "member"
},
{
"id": "user-2",
"fullName": "Jane Smith",
"role": "member"
}
],
"createdAt": "2024-01-01T12:00:00Z"
}
}
Manage Group Members
Add Members:
POST /api/Chat/groups/{conversationId}/participants
Authorization: Bearer {token}
Content-Type: application/json
{
"memberIds": ["user-4", "user-5"]
}
Remove Members:
DELETE /api/Chat/groups/{conversationId}/participants
Authorization: Bearer {token}
Update Member Role:
PUT /api/Chat/groups/{conversationId}/participants (body: role)
Authorization: Bearer {token}
Content-Type: application/json
{
"role": "admin" // or "member"
}
Group Settings
Update Group Info:
PUT /api/Chat/groups/{conversationId} (update group info)
Authorization: Bearer {token}
Content-Type: application/json
{
"name": "Updated Group Name",
"description": "Updated description",
"avatar": "new-avatar-url"
}
Leave Group:
POST /api/Chat/conversations/{conversationId}/leave (or remove self via participants endpoint)
Authorization: Bearer {token}
Message Status
Messages have different statuses that indicate their delivery state:
| Status | Description | When It Occurs |
|---|---|---|
| Sending | Message is being sent | Immediately after sending |
| Sent | Message delivered to server | After server confirms receipt |
| Delivered | Message delivered to recipient | When recipient's device receives it |
| Read | Message read by recipient | When recipient opens the message |
| Failed | Message failed to send | On error or timeout |
Status Updates
Status updates are sent via SignalR in real-time when connected:
// Message sent
{
"type": "message.sent",
"data": {
"messageId": "msg-123",
"status": "sent",
"timestamp": "2024-01-01T12:00:00Z"
}
}
// Message delivered
{
"type": "message.delivered",
"data": {
"messageId": "msg-123",
"status": "delivered",
"timestamp": "2024-01-01T12:00:05Z"
}
}
// Message read
{
"type": "message.read",
"data": {
"messageId": "msg-123",
"status": "read",
"readAt": "2024-01-01T12:00:10Z"
}
}
Message Search
Search for messages across all conversations:
GET /api/Chat/messages/search?chatId=...&searchTerm=hello&skip=0&take=50
Authorization: Bearer {token}
Query Parameters:
q- Search query (required)conversationId- Limit search to specific conversation (optional)page- Page numberlimit- Results per page
Message Management
Delete Message
Delete a message (only your own messages):
DELETE /api/Chat/conversations/{conversationId} (delete conversation) or message delete if exposed
Authorization: Bearer {token}
Edit Message
Edit a message (only your own messages, within time limit):
PUT /api/Chat/messages/{messageId} (edit message, if supported)
Authorization: Bearer {token}
Content-Type: application/json
{
"content": "Updated message content"
}
Mark as Read
Mark messages as read:
POST /api/Chat/messages/{messageId}/read
Authorization: Bearer {token}
Content-Type: application/json
{
"messageIds": ["msg-1", "msg-2"] // Optional: specific messages, or all if empty
}
Best Practices
Performance
- Use SignalR for real-time features when the ChatWorker is available
- Implement message pagination
- Cache recent conversations
- Lazy load message history
User Experience
- Show typing indicators
- Display message status
- Handle offline scenarios
- Implement message queuing for offline messages
Security
- Always authenticate SignalR connections (token via accessTokenFactory or query)
- Validate message content
- Rate limit message sending
- Sanitize user inputs
API Reference
Chat endpoints use api/Chat:
- Send message:
POST /api/Chat/messages - Get messages:
GET /api/Chat/messages?chatId=...&skip=0&take=50 - Get conversations:
GET /api/Chat/conversations - Create group:
POST /api/Chat/groups - Add/remove participants:
POST /api/Chat/groups/{conversationId}/participants,DELETE /api/Chat/groups/{conversationId}/participants - Upload media:
POST /api/Chat/upload,POST /api/Chat/upload-multiple - Mark read:
POST /api/Chat/messages/{messageId}/read - Mute/archive:
POST /api/Chat/conversations/{conversationId}/mute,.../archive
Push notifications for chat and calls
When the recipient’s tab or app is in the background, they can receive Web Push (browser) or Expo Push (mobile) for new messages and incoming calls. The API publishes chat/call events to RabbitMQ; WebPushNotificationWorker and ExpoPushNotificationWorker consume and send push. For setup, endpoints, and troubleshooting, see Web Push and Expo Push.
Related Features
- Web Push and Expo Push - Browser and mobile push for chat/call
- Notifications Feature - In-app and real-time notifications
- API Intro - Real-time (SignalR) · Architecture
- User Management - User profiles and connections