Server: Middleware
Middleware functions run before command handlers. Use them for auth, validation, logging, or injecting context.
Global middleware
Run before every command:
.(async () => {
const = await ..(.);
if (!?.userId) throw new ("Unauthorized");
.user = {
: .userId,
: .role,
};
});
Per-command middleware
Attach middleware to individual commands. These run after global middleware:
const = async () => {
const { } = .payload;
if (!?.includes("@")) throw new ("Invalid email");
};
.(
"register",
async () => {
// ctx.payload is already validated
return { : true };
},
[]
);
Middleware can be useful for things like throttling client messages:
const = new ();
.(async () => {
if (. !== "cursor-update") return;
const = .();
const = .(..) || 0;
if ( - < 50) {
throw new ("Too fast"); // or just return
}
.(.., );
});
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:
.(async () => {
const = await ..(.);
.user = {
: ?.userId,
: await fetchPermissions(?.userId),
};
});
.("admin:action", async () => {
if (!.user?.permissions.includes("admin")) {
throw new ("Admin required");
}
return { : true };
});
Client error response
If a middleware throws, the client receives an error:
{
error: "Admin required",
code: "ESERVER",
name: "Error"
}
Use cases
These are mostly pseudocode, but hopefully help illustrate what this is useful for:
Auth
.(async () => {
const = await ..(.);
if (!?.userId) throw new ("Login required");
});
Validation
const = () => {
if (!.payload?.title) throw new ("Missing title");
};
.("post:create", handler, []);
Logging
.(() => {
.(`[cmd] ${.} from ${..}`);
});
Rate limiting
const = createRateLimiter(10, 60_000); // 10 req/min
.(() => {
if (!.allow(..))
throw new ("Rate limit exceeded");
});
Last updated on