Skip to content

Realtime

Subscribe to changes in a collection over a WebSocket. Every event is re-checked against the collection’s read policy before it reaches you.

const posts = client.collection('posts');
const stop = posts.subscribe('insert', (event) => {
console.log(event.type, event.documentId, event.document);
});
// later
stop();

subscribe(event, callback) takes 'insert', 'update', 'replace', 'delete' or '*'. One lazy WebSocket per client is opened on the first subscription and multiplexed across all of them; unsubscribing the last one closes it.

There is no automatic reconnect in this version. A closed socket reopens on the next subscribe() call.

Endpoint: GET /api/v1/realtime, upgraded. A non-upgrade request gets 426 WebSocket upgrade required.

Client → server:

{ "type": "auth", "token": "<access token>" }
{ "type": "subscribe", "id": "sub1", "collection": "posts", "events": ["insert","update"] }
{ "type": "unsubscribe", "id": "sub1" }

Server → client:

{ "type": "auth_ok" }
{ "type": "subscribed", "id": "sub1" }
{ "type": "unsubscribed", "id": "sub1" }
{ "type": "event", "subscription": "sub1",
"event": { "type": "insert", "collection": "posts", "documentId": "", "document": { } } }
{ "type": "error", "code": "FORBIDDEN", "message": "", "id": "sub1" }

The first message must be auth — anything before it gets UNAUTHENTICATED with the message “send { type: “auth”, token } first“. Re-sending auth renews the token on a live socket.

events is optional and defaults to all four.

INVALID_MESSAGE · AUTH_FAILED · UNAUTHENTICATED · DUPLICATE_ID · UNKNOWN_SUBSCRIPTION · NOT_FOUND · FORBIDDEN · TOKEN_EXPIRED · CONNECTION_LIMIT

This is the guarantee that matters: the read filter is re-evaluated against every single event, not once at subscribe time. A document that stops matching your filter stops producing events for you, mid-stream.

Anything that cannot be proven visible is silently dropped — not an error, because an error would itself reveal that something changed.

Two consequences:

  • deniedReadFields are stripped from post-images before emit.
  • Subscribing requires an existing policy with a matching read rule; otherwise you get FORBIDDEN. Reserved collection names answer NOT_FOUND, matching the REST surface.

A delete event never carries document — after the fact there is nothing to filter against.

Without change-stream pre-images configured, delete events reach only subscribers whose read filter is unrestricted. If a project must deliver deletes to filtered subscribers, enable pre-images on the collection. For BYO Mongo, that requires the collMod privilege — without it, byoCapabilities.preImages is false and the project is created with a warning rather than failing.

An expired token pauses delivery: events are dropped and one TOKEN_EXPIRED error is sent. Re-authenticating on the same socket resumes it. The SDK does this transparently — it catches the code, refreshes, and re-auths.

The first successful auth pins the project. A renewal token for a different project is rejected.

MongoDB must be a replica set. Change Streams are a replica-set feature; a standalone mongod cannot serve realtime. The Docker Compose setup brings up a single-node replica set for exactly this reason.

On hosted deployments, a project at its live-connection cap answers CONNECTION_LIMIT and closes with code 1008. Note that counter is per server instance, so a multi-instance deployment effectively multiplies the allowance.