Skip to content

Heartbeats and reconnect — part of the protocol, not an add-on

Heartbeats and reconnect belong in the transport protocol from day one — not as a later patch.

nestjs-realtime-stream-heartbeats-reconnect

Why it matters

Failure modes if this rule is ignored
StakeIf ignored
Drops connection
  • Proxies and load balancers close idle connections after ~60 seconds. Without heartbeats, the client thinks it's connected but isn't.
  • Resume after refresh fails when the server never emitted event IDs for Last-Event-Id.
Bad UX
  • Silent disconnects — UI shows "streaming" while the connection is already dead.

How to fix

  • SSE: emit an empty comment (:heartbeat\n\n) every 15–30 seconds; on reconnect the client sends Last-Event-Id to resume.
  • WebSocket: rely on ping/pong (socket.io does this automatically) and exponential backoff on reconnect.
  • For durable runs, pair heartbeats with explicit cancel/resume policy (see related streaming rules).

Examples

Bad
ts
@Sse('runs/:id/events')
streamRun(@Param('id') runId: string): Observable<MessageEvent> {
  return this.runs.events(runId);
}
Good
ts
@Sse('runs/:id/events')
streamRun(@Param('id') runId: string): Observable<MessageEvent> {
  return merge(
    this.runs.events(runId),
    interval(15_000).pipe(map(() => ({ type: 'heartbeat', data: '' } as MessageEvent))),
  );
}

// frontend
const sse = new EventSource(`/runs/${runId}/events`);
// EventSource auto-reconnects with Last-Event-Id when the server emits `id:` on every event

Contribute

Released under the MIT License.

esc