Examples
Three shapes cover most of what people build on Socketly. All of these are running live against the edge from this page — nothing below is a mockup.
Notifications
Something happens on your backend and a browser that was doing nothing finds out. No one is typing here: the buttons call this site's server, and what appears is what came back over the socket.
public-docs-notificationsThese buttons call this site's server, not the panel below. What appears is what came back over the socket.
Nothing yet. Trigger one and it arrives here without the page reloading.
Your backend publishes when the thing actually happens:
// Somewhere in your backend, after the thing actually happened.
await fetch(`https://api.socketly.co/v1/apps/${APP_ID}/events`, {
method: 'POST',
headers: {
'content-type': 'application/json',
authorization: `Bearer ${process.env.SOCKETLY_SECRET}`,
},
body: JSON.stringify({
channels: [`private-user-${user.id}`],
event: 'notification',
data: { kind: 'payment', title: 'Payment received', body: '$49.00 from Acme Inc' },
}),
});And the browser listens. There is no polling and no page to refresh:
'use client';
import { useChannel } from '@socketly/react';
import { toast } from 'sonner';
export function Notifications({ userId }: { userId: string }) {
useChannel(`private-user-${userId}`, {
notification: ({ data }) => toast(data.title, { description: data.body }),
});
return null; // Nothing to render — it just listens.
}Use a private-user-… channel so a notification only reaches the person it is about. A public- channel would deliver it to anyone who guessed the name.
Progress on a long job
The case polling handles worst. A job runs for a while and the browser needs to know where it got to — polling means choosing between a stale bar and hammering an endpoint, while a socket lets the server speak when there is something to say.
public-docs-jobsStart one. Each step below arrives as the server finishes it — nothing is polling.
The worker publishes as it finishes each stage:
// In your worker, as each stage completes.
for (const [i, step] of steps.entries()) {
await runStep(step);
await socketly.trigger(`private-job-${jobId}`, 'job-progress', {
index: i + 1,
total: steps.length,
label: step.label,
status: 'done',
});
}'use client';
import { useState } from 'react';
import { useChannel } from '@socketly/react';
export function JobProgress({ jobId }: { jobId: string }) {
const [done, setDone] = useState(0);
const [total, setTotal] = useState(0);
useChannel(`private-job-${jobId}`, {
'job-progress': ({ data }) => {
setTotal(data.total);
if (data.status === 'done') setDone(data.index);
},
});
return <progress value={done} max={total || 1} />;
}Channel per job, not per user: someone watching two deployments should get two independent progress bars, and a channel scoped to the job gives you that for free.
Chat and presence
Two clients on the same channel, each with its own connection. Sending from one reaches the other because it went to the server and came back.
public-docs-demoNothing yet. Send from either side.
Nothing yet. Send from either side.
Chat is the case that needs presence- channels, since you want the roster as well as the messages. The quickstart builds exactly this.