Skip to main content
nestrs-core ships a thin Server-Sent Events wrapper behind the sse feature. The intent is the same as the nestrs-oauth2::cookies surface: re-export the axum primitive (Sse<S>), give it a stable, axum-agnostic return type (SseResponse<S>), and add a conversion surface that covers the payload shapes handler authors actually reach for — &str, String, Bytes, and anything Serialize via serialize_to_event. Handlers can stream events without ever naming axum::response::sse.

Enable the feature

On the umbrella crate:
Or on nestrs-core directly:
The flag pulls in bytes, futures-core, and serde on top of the axum SSE plumbing, which is on by default in axum 0.7. Enable it only if you actually emit SSE streams; outbound clients that consume SSE payloads don’t need this flag.

Return an SSE stream

axum 0.7’s Sse::new takes Stream<Item = Result<Event, E>>. Both from_stream and from_fallible_stream are that constructor — there is no infallible-item overload.
SseResponse<S> is a newtype around axum::response::sse::Sse<S> that implements IntoResponse directly, so handlers can return it without naming axum’s SSE type. The wrapper delegates the response conversion to axum, so behavior (the text/event-stream content-type, chunked transfer encoding, retry semantics) stays identical.

IntoSseEvent — payload shapes

The trait covers the shapes that convert without going through serde. JSON payloads use the free function serialize_to_event instead of a blanket T: Serialize impl — axum::response::sse::Event itself implements Serialize, so a blanket impl would conflict with the passthrough.

serialize_to_event

Any Serialize type becomes an SSE event with the default message name. Serialization failures (e.g. a f64::NAN that serde_json rejects) are not a stream crash — the helper emits an event: error event with the failure message instead:
axum’s Event fields are private — assert on the serialized body (data: … / event: error) rather than evt.event. This is a deliberately conservative default — SSE consumers typically expect a long-lived stream and treat any termination as an implicit error. If you’d rather crash the stream on serialization failure, build the Event yourself with serde_json::to_string(...) and surface the Err from your stream directly.

Fallible streams

For producers that want a structured error path (a network drop, a serialization failure the caller does want to crash on), pass a Result stream. from_fallible_stream is an alias of from_stream so call sites that already produce Result read as intent:
Result<Event, E> is axum’s contract — E must convert to Box<dyn Error + Send + Sync>.

KeepAlive

Attach a heartbeat policy with .keep_alive(...). The default KeepAlive::new() sends a comment line every 15 seconds — long enough to keep middlebox connections warm, short enough that the consumer sees activity when the producer goes quiet:

Conversion from an existing Sse

If you’ve already constructed an axum::response::sse::Sse<S> (e.g. from third-party code), wrap it via From:

Accessors

When the wrapper hides something you need (a custom header, a non-standard content-type), drop down to the inner Sse<S> with into_inner or borrow it with as_inner:

Why these choices

  • Stable return type — handlers return SseResponse<S> regardless of which crate (nestrs, nestrs-http) wired the response. Tests can construct a response directly without depending on the full runtime.
  • IntoSseEvent plus serialize_to_event — strings and bytes stay on the trait; JSON goes through a free function so Event’s own Serialize impl can still pass through as an event.
  • Serialize-failure → error event — SSE consumers treat stream termination as a connection drop, so silently emitting an error event keeps the stream alive and lets the consumer decide what to do. Crashing the producer would be the more disruptive default.
  • Behind a feature flagsse is off by default. Apps that emit SSE streams opt in; apps that consume SSE payloads from upstream services stay on the default feature set.

See also