Server-Sent Events
SSE is the browser-facing streaming transport built directly on the event bus and event definition model.
Use it when the server needs to push updates to clients but clients do not need a bidirectional socket.
These examples extend the starter app and the
blog:post.published event from
Events and the Event Bus.
A copyable SSE endpoint
Section titled “A copyable SSE endpoint”// @skip-typecheckimport { defineApp } from '@lastshotlabs/slingshot';import { blogEventsPlugin } from './blog/events';import { blogPackage } from './blog/package';
export default defineApp({ packages: [blogPackage], plugins: [blogEventsPlugin], sse: { endpoints: { '/events': { events: ['blog:post.published'], heartbeat: 15_000, filter: (client, _event, payload) => client.actor.id !== null && (payload as { authorId: string }).authorId === client.actor.id, }, }, },});How SSE fits into the model
Section titled “How SSE fits into the model”An SSE endpoint does not publish events itself. It subscribes to the app event bus and streams events whose definitions allow that exposure.
That means SSE depends on:
- event definitions
- event envelope scope
- client-safe exposure rules
- optional endpoint-specific filtering
Why event definitions matter here
Section titled “Why event definitions matter here”Only events that were explicitly defined and exposed should reach browser clients.
Typical flow:
- register an event with
defineEvent(...) - mark it
client-safe - publish it with
ctx.events.publish(...) - let SSE stream it to matching subscribers
This keeps browser delivery aligned with the same canonical event model used elsewhere.
Browser client example
Section titled “Browser client example”const source = new EventSource('/events');
source.addEventListener('blog:post.published', event => { const payload = JSON.parse(event.data) as { postId: string; title: string; slug: string; };
console.log('new published post', payload.slug, payload.title);});Upgrade and filtering
Section titled “Upgrade and filtering”Each endpoint can also define:
upgrade(req)to accept or reject the connection and attach client metadatafilter(client, event, payload)for per-client delivery controlheartbeatto keep connections alive
That gives you a clean split:
- event definitions control what is externally exposable
- endpoint filters control which subscribed client gets which exposed event
Progression
Section titled “Progression”- default: expose a client-safe event on one SSE endpoint
- customize: add
upgrade(req)orfilter(client, event, payload)for finer per-client control - escape hatch: change the underlying event bus transport when SSE needs cross-instance delivery
Use SSE for
Section titled “Use SSE for”- notifications
- activity feeds
- dashboards
- browser event streams where
EventSourceis enough
Do not treat SSE as WebSockets-lite
Section titled “Do not treat SSE as WebSockets-lite”SSE is simpler because it is one-way. It does not replace:
- room subscriptions
- client-to-server events
- presence
- connection recovery for bidirectional sessions
Those belong to WebSockets.