Real Time
RestAPI.com provides real-time capabilities for detecting database changes and enabling direct client-to-client communication.
Prerequisites
To use real-time subscriptions on a collection, you must configure LISTEN access:
{ "method": "LISTEN", "roleNames": ["_AUTHENTICATED_USER"] }
Without LISTEN access, clients cannot subscribe to changes on that collection. Configure this in the Developer Portal under your collection's Access settings, or in your schema JSON.
Features
- Database change detection — Subscribe to collection changes as they happen
- Client-to-client messaging — Direct peer-to-peer communication via channels
- Server-to-client messaging — Push updates from functions to connected clients
Connection Options
Choose your preferred protocol:
| Protocol | Library |
|---|---|
| Azure Web PubSub | @azure/web-pubsub-client |
| SignalR | @microsoft/signalr |
Setting Up Connections
Azure Web PubSub
import { WebPubSubClient } from '@azure/web-pubsub-client';
const client = new WebPubSubClient({
getClientAccessUrl: async () => {
const response = await fetch('/api/_rt/ws');
return response.text();
}
});
await client.start();
SignalR
import * as signalR from '@microsoft/signalr';
const connection = new signalR.HubConnectionBuilder()
.withUrl('/api/_rt/signalr')
.withAutomaticReconnect()
.build();
await connection.start();
Listening for Changes
SignalR
connection.on('change', (data) => {
console.log('Data changed:', data);
});
Web PubSub
client.on('group-message', (event) => {
console.log('Message received:', event.message);
});
Entity Subscriptions
Subscribe to database changes on a collection using the /_listen endpoint:
await fetch('/api/products/_listen', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
connectionId: myConnectionId,
type: 'ws' // or 'signalr'
})
});
Entity subscriptions are read-only — clients receive notifications but cannot send messages to entity channels. This ensures data integrity since entity notifications are controlled by the server.
Filtering Subscriptions
Subscribe to a specific item:
body: JSON.stringify({
connectionId: myConnectionId,
type: 'ws',
id: 'item-uuid' // Only receive changes for this item
})
Filter by lookup relationships:
body: JSON.stringify({
connectionId: myConnectionId,
type: 'ws',
lookupIds: ['parent-uuid-1', 'parent-uuid-2'] // Filter by parent references
})
Channel-Based Communication
Channels enable direct client-to-client messaging. There are two types:
Regular Channels
Standard channels where clients can join and send messages freely:
// Join a channel
await fetch('/api/_rt/join', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
connectionId: myConnectionId,
channel: 'my-room'
})
});
Sending Messages
For the best performance, send messages directly through Web PubSub or SignalR rather than using the /_rt/send API endpoint. Direct communication bypasses the HTTP layer entirely, resulting in lower latency and better throughput.
Recommended: Direct via Web PubSub or SignalR
// Web PubSub - channel prefixed with API name
client.sendToGroup('my-api:my-room', { type: 'chat', text: 'Hello!' });
// SignalR
connection.invoke('SendToGroup', 'my-api:my-room', { type: 'chat', text: 'Hello!' });
Alternative: Via /_rt/send API
Only use the /_rt/send endpoint when:
- Sending from server-side functions (which don't have a WebSocket connection)
- The client is not connected to Web PubSub or SignalR
await fetch('/api/_rt/send', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
channel: 'my-room',
body: { type: 'chat', text: 'Hello!' }
})
});
Private Channels (Server-Controlled)
Channels with : in the name are private channels — they can only be managed via server-side functions. Use these when you need to control who can join or send messages.
Functions use the same /_rt/join and /_rt/send endpoints as clients. The difference is that functions can access private channels (names containing :) while clients cannot.
Managing Private Channels with Invocable Functions
The recommended pattern is to create an invocable function that validates access and manages channel membership:
// Function: join-room (invocable)
// Client calls: POST /api/_invoke/join-room
const roomId = req.bodyJson.roomId;
const connectionId = req.bodyJson.connectionId;
// Validate access (e.g., check if user is allowed in this room)
const room = await get(`rooms/${roomId}`);
if (!room.members.includes(me.id)) {
res = { status: 403, bodyJson: { error: 'Not a member of this room' } };
return;
}
// Add client to private channel
await post('/_rt/join', {
connectionId: connectionId,
channel: `room:${roomId}`,
readOnly: false // Client can send messages
});
res = { status: 200, bodyJson: { joined: true } };
Client code:
// Get WebSocket connection first
const wsResponse = await fetch('/api/_rt/ws');
const { url } = await wsResponse.json();
const client = new WebPubSubClient(url);
await client.start();
// Request to join a private channel via invocable function
await fetch('/api/_invoke/join-room', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
roomId: 'my-room-id',
connectionId: client.connectionId
})
});
// Now client can send/receive on the private channel
The readOnly Parameter
Control whether clients can send messages after joining:
// Client can send and receive
await post('/_rt/join', {
connectionId: connectionId,
channel: 'chat:room-123',
readOnly: false
});
// Client can only receive (broadcast pattern)
await post('/_rt/join', {
connectionId: connectionId,
channel: 'announcements:system',
readOnly: true
});
Sending from Functions
Functions can send to any channel, including private channels:
await post('/_rt/send', {
channel: 'announcements:system',
body: { type: 'alert', text: 'Server maintenance in 10 minutes' }
});
Why Clients Cannot Access Private Channels
Clients are blocked from joining or sending to channels containing ::
// This will fail with 400 Bad Request
await fetch('/api/_rt/join', {
body: JSON.stringify({
connectionId: myConnectionId,
channel: 'room:123' // Rejected - contains ':'
})
});
This ensures your function logic always controls access to private channels.
Channel Type Summary
| Channel Type | Client Join | Client Send | Function Join | Function Send |
|---|---|---|---|---|
Regular (my-room) | Yes | Yes | Yes | Yes |
Private (room:123) | No | Via function | Yes | Yes |
Entity (/_listen) | Via endpoint | No | N/A | Automatic |
Channel Naming Rules
- Channel names with
:are private (function-controlled only) - Channel names cannot match collection names (use
/_listenfor entities) - Channel names are scoped to your API (internally prefixed with
api-name:)
Endpoints
| Endpoint | Method | Description |
|---|---|---|
/api/_rt/ws | GET | Get WebSocket URL for Web PubSub |
/api/_rt/signalr | GET | Get SignalR connection info |
/api/_rt/join | POST | Join a channel |
/api/_rt/send | POST | Send message to a channel |
/api/{collection}/_listen | POST | Subscribe to entity changes |
Use Cases
- Live dashboards — Update metrics in real time
- Chat applications — Direct messaging between users
- Collaborative editing — Sync changes across clients
- Notifications — Push updates to connected users
- Gaming — Real-time game state synchronization