Build an SSE stream
Name and version events
Design event names and payloads that can evolve without making every client guess what changed.
A live feed is a public API. If every message is a generic { message: ... } blob, you cannot grep logs, filter in the client, or add fields without breaking old tabs.
Give each event a stable type, a monotonic id, a version, and a payload shape you document.
A small envelope
We emit incident.created and incident.updated instead of one anonymous stream:
event: incident.updated
id: evt_86
data: {"v":1,"incidentId":42,"status":"resolved","occurredAt":"2026-09-08T14:05:00Z"}
On the server, build the lines from one object:
function writeIncidentUpdated(res, event) {
res.write(`event: incident.updated\n`)
res.write(`id: ${event.id}\n`)
res.write(`data: ${JSON.stringify({
v: 1,
incidentId: event.incidentId,
status: event.status,
occurredAt: event.occurredAt,
})}\n\n`)
}
The v field is the payload version. When we add an optional impact string in v2, v1 clients ignore unknown fields and keep working.
Client handling
source.addEventListener('incident.updated', (event) => {
const payload = JSON.parse(event.data)
if (payload.v > 2) {
console.warn('unknown payload version', payload.v)
return
}
applyIncidentUpdate(payload)
})
Feed an older client a payload with a new optional field. The board should still update title and status. That is forward compatibility you can test.
Do not mirror tables blindly
Dumping raw database rows into the stream leaks internal columns and couples every schema change to every open tab. Map to a deliberate public shape at the edge.
Write three examples in your project notes: create, update, and resolve. Show how a v1 client treats a v2 optional field.
Naming convention
Use past-tense domain names separated by dots: incident.created, not createIncident. Logs, metrics, and client filters all read cleaner when the type string matches the product language.
Publish a one-page event catalog in the repo. Future you will forget whether incident.update or incident.updated is canonical.
Try this on your own project: rename one generic event type, add id and v, and verify an older script still applies the fields it understands.
Lesson completed