Skip to Content
Server SDKMiddleware

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

  1. Global middleware (in the order registered)
  2. Per-command middleware
  3. 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
© 2025