Guide

Real-time (LiveQuery)

Clients subscribe to a query and receive updates as rows change — with the same ACL deciding who sees what.

What it is

A client sends a query and keeps the connection open. When a row that matches is created, changed or removed, the server pushes the event. No polling, no websocket layer to design, and — the part that matters — the same ACL applies, so a subscriber only receives rows they were already allowed to read.

Enabling it

Two settings on Parse Server: which classes may be subscribed to, and the websocket server itself.

const parseServer = ParseServer({
  // … your usual options …
  liveQuery: { classNames: ['Task', 'Notification'] },
});

// after listen()
const httpServer = app.listen(1337);
ParseServer.createLiveQueryServer(httpServer);
Only listed classes are live A class missing from classNames simply never emits. Subscribing to it succeeds and nothing ever arrives — one of those failures with no error attached.

Across several instances, LiveQuery needs Redis so an event on one is delivered by all — set liveQuery.redisURL. A single instance does not need it.

Subscribing, from a client

const query = new Parse.Query(Task);
query.equalTo('project', Project.pointer(projectId));
query.equalTo('done', false);

const subscription = await query.subscribe();

subscription.on('create', task => { // added, and matches });
subscription.on('update', task => { // changed, still matches });
subscription.on('enter',  task => { // changed INTO matching });
subscription.on('leave',  task => { // changed OUT of matching });
subscription.on('delete', task => { // removed });

subscription.unsubscribe();
EventMeans
createA new row that matches the query
updateA matching row changed and still matches
enterAn existing row changed so that it now matches
leaveIt changed so that it no longer matches
deleteA matching row was deleted

enter and leave are the ones people miss. Marking a task done when the subscription filters done: false emits leave, not update — a list that only handles update will never remove it.

Permissions

A subscription carries the client's session token, and every event is checked against the row's ACL before it is sent. Two users subscribed to the same query receive different events, because they may read different rows.

Nothing extra to write The work you did in Permissions & ACL covers real-time too. There is no second authorisation layer for websockets — which is exactly where hand-built real-time usually leaks.

Server-side hooks

import {
  ParseClass, BaseModel, BeforeConnect, BeforeSubscribe, AfterEvent,
} from 'parse-server-kit';

@ParseClass('Task')
export default class Task extends BaseModel {

  @BeforeConnect()
  static async onConnect(req: any) {
    // Refuse the websocket entirely - runs once per connection.
    if (!req.user) throw new Parse.Error(101, 'Sign in first');
  }

  @BeforeSubscribe()
  static async onSubscribe(req: any) {
    // Narrow what a subscriber may watch, before it starts.
    if (!req.user) throw new Parse.Error(101, 'Sign in first');
    req.query.equalTo('archived', false);
  }

  @AfterEvent()
  static async onEvent(req: any) {
    // Inspect or adjust an event before it is delivered.
    // req.event is create | update | enter | leave | delete
  }
}

@BeforeSubscribe is the useful one: constraints you add there apply to every subscriber, so a client cannot widen its subscription beyond what you allow.

What it costs

When not to use it

NeedBetter
Notify a user who is not on the pagePush notification
Refresh a dashboard every minutePolling — simpler and cheaper
Stream a high-frequency feedA dedicated channel; LiveQuery matches per save
React to a change server-side@AfterSave, which needs no connection

For the client API, adapters and Redis configuration, see the Parse LiveQuery guide.