Server: Middleware
Middleware functions run before command handlers. Use them for auth, validation, logging, or injecting context.
Global middleware
Run before every command:
server.useMiddleware(async (ctx) => {
const meta = await server.connectionManager.getMetadata(ctx.connection);
if (!meta?.userId) throw new Error("Unauthorized");
ctx.user = {
id: meta.userId,
role: meta.role,
};
});
Per-command middleware
Attach middleware to individual commands. These run after global middleware:
const validate = async (ctx) => {
const { email } = ctx.payload;
if (!email?.includes("@")) throw new Error("Invalid email");
};
server.exposeCommand(
"register",
async (ctx) => {
// ctx.payload is already validated
return { success: true };
},
[validate]
);
Middleware can be useful for things like throttling client messages:
const lastSent = new Map();
server.useMiddleware(async (ctx) => {
if (ctx.command !== "cursor-update") return;
const now = Date.now();
const last = lastSent.get(ctx.connection.id) || 0;
if (now - last < 50) {
throw new Error("Too fast"); // or just return
}
lastSent.set(ctx.connection.id, now);
});
Middleware order
- Global middleware (in the order registered)
- Per-command middleware
- Command handler
If any middleware throws, the error is sent back to the client.
Modifying context
You can attach custom fields to ctx
for downstream use:
server.useMiddleware(async (ctx) => {
const meta = await server.connectionManager.getMetadata(ctx.connection);
ctx.user = {
id: meta?.userId,
permissions: await fetchPermissions(meta?.userId),
};
});
server.exposeCommand("admin:action", async (ctx) => {
if (!ctx.user?.permissions.includes("admin")) {
throw new Error("Admin required");
}
return { success: true };
});
Client error response
If a middleware throws, the client receives an error:
{
error: "Admin required",
code: "ESERVER",
name: "Error"
}
Use cases
-
Auth
server.useMiddleware(async (ctx) => { const meta = await server.connectionManager.getMetadata(ctx.connection); if (!meta?.userId) throw new Error("Login required"); });
-
Validation
const requireTitle = (ctx) => { if (!ctx.payload?.title) throw new Error("Missing title"); }; server.exposeCommand("post:create", handler, [requireTitle]);
-
Logging
server.useMiddleware((ctx) => { console.log(`[cmd] ${ctx.command} from ${ctx.connection.id}`); });
-
Rate limiting
const limiter = createRateLimiter(10, 60_000); // 10 req/min server.useMiddleware((ctx) => { if (!limiter.allow(ctx.connection.id)) throw new Error("Rate limit exceeded"); });
Last updated on