How to Add WebSockets
WebSocket events are defined in src/sockets/<domain>/index.ts using SocketRouter. They're separate from HTTP routes
but follow the same domain-based organization.
Defining events
Use createSocketRouter() to declare emitters (server pushes to client) and listeners (client sends to server):
import { createSocketRouter } from "@backend/libs/websocket/socket-router";
import { wsValidate } from "@backend/libs/websocket/ws-validate";
import { z } from "zod";
const markReadSchema = z.object({ notificationId: z.string() });
export const notificationSockets = createSocketRouter()
.emitter("new")<WsNotificationPayload>()
.emitter("count")<WsUnreadCountPayload>()
.listener("mark-read", wsValidate(markReadSchema), async (c) => {
await markReadMutation({
notificationId: c.data.notificationId,
userId: c.userId,
});
});- Emitters are type-only declarations. The actual push happens via
wsEmitin your feature code. - Listeners have an inline handler that processes incoming client messages. Use
wsValidate(schema)for Zod validation.
Registering socket routes
Mount socket routers in src/sockets/index.ts:
const sockets = new SocketRouter({ authenticate: wsAuthenticate })
.socket("/notification", notificationSockets)
.socket("/invitation", invitationSockets);
export const registerSockets = (app: Hono) => {
app.route("/ws", sockets.routes());
};Emitting from features
Use wsEmit from @backend/sockets/emit for type-safe server-side pushes:
import { wsEmit } from "@backend/sockets/emit";
wsEmit.toUser({ userId, topic: "notification/new", data: notification });
wsEmit.toUsers({ userIds, topic: "notification/new", data: notification });
wsEmit.toOrganization({ organizationId, topic: "invitation/new", data: invitation });
wsEmit.broadcast({ topic: "notification/count", data: { unreadCount: 0 } });Topics and payloads are type-checked at compile time. A typo in the topic or wrong payload shape is a type error.
Frontend integration
Socket event types are exported from the backend and picked up by the frontend's socketSdk automatically. No manual
type wiring needed. See Frontend WebSockets.
Adding a new socket event
- Create
src/sockets/<domain>/index.tswithcreateSocketRouter() - Declare emitters and listeners
- Register in
src/sockets/index.tswith.socket("/prefix", router) - Export payload types for the frontend
- Use
wsEmitin your feature code to push events
Every purchase and sponsorship funds my 8+ years of work on open source given freely to the community. It keeps the lights on, funds new packs, and keeps the ecosystem alive. Even a small tier means a lot. Thank you!
