Server: Commands
Expose structured request/response handlers that clients can call over WebSocket.
Basic usage
server.exposeCommand("echo", async (ctx) => {
return `echo: ${ctx.payload}`;
});
Clients can call this command with:
const response = await client.command("echo", "Hello!");
console.log(response); // "echo: Hello!"
Command handlers receive an object of type MeshContext
with the following properties:
ctx.command
— command namectx.payload
— data sent by the clientctx.connection
— the WebSocket connection- Additional fields added by middleware
You can return any JSON-serializable value.
server.exposeCommand("add", (ctx) => {
return ctx.payload.a + ctx.payload.b;
});
Errors
Throwing inside a command sends the error back to the client in a predictable format.
server.exposeCommand("divide", (ctx) => {
const { a, b } = ctx.payload;
if (b === 0) throw new Error("Cannot divide by zero");
return a / b;
});
The client receives:
{
error: "Cannot divide by zero",
code: "ESERVER",
name: "Error"
}
To customize the error code and name:
import { CodeError } from "@mesh-kit/core/client";
server.exposeCommand("login", () => {
throw new CodeError("Invalid credentials", "EAUTH", "AuthError");
});
Client receives:
{
error: "Invalid credentials",
code: "EAUTH",
name: "AuthError"
}
Or if you prefer, you can return an object with error
, code
and name
properties instead of throwing anything. It’s recommended to stick to this error format for the sake of consistency.
Middleware
Attach middleware to validate input, enforce auth, or inject data:
const validate = (ctx) => {
if (!ctx.payload.email.includes("@")) throw new Error("Invalid email");
};
server.exposeCommand("update-profile", async (ctx) => {
return { success: true };
}, [validate]);
See middleware for more details.
Built-in commands
Mesh includes built-in commands for rooms, presence, channels, metadata, and records:
"mesh/join-room"
,"mesh/leave-room"
"mesh/subscribe-presence"
,"mesh/unsubscribe-presence"
"mesh/publish-presence-state"
,"mesh/clear-presence-state"
"mesh/subscribe-channel"
,"mesh/unsubscribe-channel"
"mesh/get-connection-metadata"
,"mesh/get-my-connection-metadata"
"mesh/get-room-metadata"
"mesh/subscribe-record"
,"mesh/unsubscribe-record"
"mesh/publish-record-update"
These power the SDK’s features and can be intercepted with middleware if needed.
For example, you could:
- Prevent access to certain rooms based on user roles
- Validate presence state schema before publishing
- Enforce metadata structure for all connections
- Restrict record updates to certain users
- Control access to channels based on user permissions
- Validate record update schema with middleware
Tips
- Use namespaced commands like
"chat:send"
or"user:create"
- Validate inputs with middleware
- Keep handlers simple and focused
- Return only what the client needs