Realtime (Ably)
In one line: Ably holds an open WebSocket in the browser and pushes each
change to everyone who should see it in ~50ms — booking status, schedule, and
leave. Publishing is server-side only; clients get subscribe-only Token Auth
scoped to their role. It degrades gracefully when
ABLY_PRIVATE_KEY is absent.Ably is a guarded extension point. The server helper reads
ABLY_PRIVATE_KEY directly behind a truthy guard. With the key absent it
returns null, POST /api/ably/token answers 503, and the client falls
back to its normal data fetch. The app builds and runs with no realtime
configured — live updates simply switch on once the key is present.What it is
A thin live layer over the normal app. The page still loads its data the usual way; Ably only delivers the changes that happen while the page is open. Two audiences benefit:- Customers see their booking cards update in place — status, date, and the assigned stylist — without touching anything.
- Staff and admins see new bookings drop into the pending queue, the schedule grid fill and free up, and leave requests appear as they are made.
How it works
1
Page mounts
A
useEffect opens an Ably connection using an authCallback that fetches a
token from our own API.2
Token is issued
POST /api/ably/token returns a short-lived, scoped Ably JWT for the signed-in
user (see Token Auth).3
Subscribe
The page subscribes to exactly the channels it is allowed to read and updates
React state when a message arrives.
4
Server publishes
After a state-changing API route commits its database write, it publishes to
every relevant channel using
ABLY_PRIVATE_KEY.5
Page unmounts
The connection is cleaned up. Ably handles reconnect on network loss and calls
authCallback again when the token expires.All publishing is server-side only. Browsers never hold a key with publish
capability — they receive a subscribe-only token. This prevents anyone from
spoofing a status change or publishing to a channel from the client.
Channels
Channels follow a predictable naming convention so a token can be scoped with wildcards.
The customer always subscribes to
customer:{userId}:bookings on the bookings
list, and to booking:{bookingId} on a booking detail. Admin dashboards
subscribe to admin:bookings:{branchId}; the schedule page subscribes to
admin:schedule:{selectedDate}.
Example events
A representative slice of the events flowing over these channels:Token Auth
Every client uses Token Auth — never a raw key. The server mints a short-lived Ably JWT per authenticated user, scoped to exactly the channels that user’s role may read.POST /api/ably/token is a session-protected route; the
clientId on the token is the caller’s user id.
- Customer
- Admin
- Staff
A customer is granted
subscribe on their own namespace only, plus the specific
booking channels they own. They never receive a wildcard over all booking:*
channels, so they cannot watch another customer’s booking.Publishing flow
State-changing API routes publish to every relevant channel after the database write commits, so a subscriber never sees a change that did not actually persist.admin:schedule:{date}, removing the appointment on the staff channel, and
moving the card on admin:bookings:{branchId}. Because each publish is
fire-and-forget after the commit, a realtime hiccup never blocks or fails the
underlying booking action.
Related links
Notifications & Realtime API
The POST /api/ably/token route and its scoped capabilities.
Notifications & Email
Web Push and email — the other half of the communication layer.
Background Jobs
Scheduled work that also drives realtime and push updates.
Environment Variables
ABLY_PRIVATE_KEY and the rest of the realtime config.