Docs / WebSockets & SSE
WebSockets & SSE
Realtime endpoints in route files — WS exports and the sse() helper.
Added in 1.0
Two realtime primitives, both living in normal route files.
WebSockets
Export WS from a route file. The path of the file is the WebSocket URL:
// app/routes/ws/rooms/[room].ts
import { defineWebSocket } from '@hopak/core';
export const WS = defineWebSocket({
open(ws) {
ws.subscribe(ws.data.params.room);
},
message(ws, message) {
ws.publish(ws.data.params.room, message);
},
close(ws) {
ws.unsubscribe(ws.data.params.room);
},
});
// client
const socket = new WebSocket('ws://localhost:3000/ws/rooms/lobby?as=ada');
ws.data.params— route params ([room]→ws.data.params.room).ws.data.query— the parsed query string.wsis Bun’s nativeServerWebSocket—send,subscribe,publish, backpressure viadrain, all available.- A plain HTTP request to a WS-only path answers
426 Upgrade Required. A file can also export regularGET/POSThandlers next toWS.
Auth on WebSockets
The upgrade runs after the before middleware chain, so the same guards that protect HTTP routes protect sockets:
// app/routes/ws/feed.ts
import { defineRoute, defineWebSocket } from '@hopak/core';
import { requireAuth } from '../../middleware/auth';
export const GET = defineRoute({
handler: () => new Response(null, { status: 426 }),
before: [requireAuth()],
});
export const WS = defineWebSocket({
open(ws) { /* only authenticated clients reach here */ },
});
Server-Sent Events
For one-directional streams, sse() builds the handler:
// app/routes/events.ts
import { defineRoute, sse } from '@hopak/core';
export const GET = defineRoute({
handler: sse(async (stream, ctx) => {
stream.send({ hello: ctx.query.get('name') ?? 'world' });
const timer = setInterval(() => {
stream.send({ tick: Date.now() }, { event: 'tick' });
}, 1000);
await stream.closed; // resolves when the client disconnects
clearInterval(timer);
}),
});
stream.send(data, { event?, id? })— objects are JSON-encoded, strings pass through.stream.closed— a promise that resolves on disconnect; await it to stop producing.- Sends after disconnect are dropped silently.