Server-side Usage
Use a trusted Node process to sign credentials and to publish and receive realtime messages.
Before you start: complete the Quickstart and keep its browser subscriber connected. Use the same Celeris app credentials. This example runs only on your trusted backend, never in the browser.
1. Understand the two responsibilities
@useceleris/server signs access. It does not provide a separate publish transport. Your backend uses @useceleris/client to connect and publish, just as a browser does, but it can obtain credentials locally through createCredentialProvider().
For browser authentication, continue with an authenticated HTTP credential endpoint instead. See Authentication.
2. Create a worker
The Quickstart already installs both packages. Add worker.mjs to that project's root:
import {
createClient,
readText,
textPayload,
ServerError,
} from "@useceleris/client";
import { createSigner, createCredentialProvider } from "@useceleris/server";
const { CELERIS_CLIENT_ID, CELERIS_SIGNING_SECRET } = process.env;
if (!CELERIS_CLIENT_ID || !CELERIS_SIGNING_SECRET) {
throw new Error("Set both Celeris values in the server .env file.");
}
const signer = createSigner({
clientId: CELERIS_CLIENT_ID,
signingSecret: CELERIS_SIGNING_SECRET,
});
const client = createClient({
credentialProvider: createCredentialProvider({
signer,
claims: (request) => {
if (request.channelReference !== "demo-room") {
throw new Error("Worker channel is not allowed.");
}
return {
channels: { kind: "restricted", references: ["demo-room"] },
permissions: {
kind: "restricted",
segments: [{ segmentId: "chat", read: true, write: true }],
},
reference: "demo-worker",
allowEcho: false,
replay:
request.replayLookbackMs === undefined
? false
: { lookbackMs: Math.min(request.replayLookbackMs, 30_000) },
};
},
}),
});
const channel = client.channel("demo-room");
const chat = channel.segment("chat");
const stopErrors = channel.events().onError((error) => {
console.error(
error instanceof ServerError ? error.type : error.code,
error.message
);
});
const stopMessages = chat.onMessage((payload) =>
console.log("Received:", readText(payload))
);
const stopRecovery = channel.events().onRecovery(() => {
console.log(
"Recovered; reload authoritative state if your application needs it."
);
});
const membership = chat.subscribe();
async function shutdown() {
await channel.close();
membership.cancel();
stopMessages();
stopRecovery();
stopErrors();
}
process.once("SIGINT", () => {
void shutdown().catch(() => {
process.exitCode = 1;
});
});
process.once("SIGTERM", () => {
void shutdown().catch(() => {
process.exitCode = 1;
});
});
try {
await channel.connect();
await chat.publish({ payload: textPayload("Hello from the backend") });
console.log(
"Handed to the socket. Send a browser reply, or press Ctrl+C to close."
);
} catch (error) {
console.error(error.code ?? "Error", error.message);
await shutdown();
process.exitCode = 1;
}
The claims callback is the authority. Nothing automatically turns the request into permission. This worker explicitly restricts its channel and segment and caps requested replay at 30 seconds.
3. Run the worker
From the project root:
node --env-file=.env worker.mjs
The connected browser then receives demo-worker: Hello from the backend. Send a browser reply. The worker then prints Received: followed by the text. Press Ctrl+C to close it.
The worker logs local send completion, not confirmed delivery. If the browser was not connected or subscribed when the worker sent, nothing resends that publish.
4. Apply this to your service
Sign from server-owned configuration. For multi-tenant services, authorize each channel. Do not replace the restriction with unrestricted claims.
The provider calls your claims callback on every attempt and signs freshly. It honors cancellation before it signs. signer.sign() itself is synchronous and takes no abort signal. The server package has no socket to dispose. The client channel has one.
Use Reconnection and Recovery to decide how your service reconciles missed or duplicate messages. A publish has the same local-acceptance semantics on servers as in browsers.
Next: Server API and Client API.