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,
    });
  });

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

  1. Create src/sockets/<domain>/index.ts with createSocketRouter()
  2. Declare emitters and listeners
  3. Register in src/sockets/index.ts with .socket("/prefix", router)
  4. Export payload types for the frontend
  5. Use wsEmit in your feature code to push events