rust 50 lines · 7 steps

Streaming import progress with SSE in Axum

An Axum handler turns a watch channel into a Server-Sent Events stream that ends once a job reaches a terminal state.

Explained by highlit
1use axum::{
2 extract::{Path, State},
3 response::sse::{Event, KeepAlive, Sse},
4};
5use futures::stream::Stream;
6use serde::Serialize;
7use std::{convert::Infallible, time::Duration};
8use tokio_stream::wrappers::WatchStream;
9use tokio_stream::StreamExt;
10 
11#[derive(Clone, Serialize)]
12#[serde(tag = "status", rename_all = "snake_case")]
13pub enum ImportProgress {
14 Queued,
15 Running { processed: u64, total: u64 },
16 Completed { imported: u64 },
17 Failed { reason: String },
18}
19 
20pub async fn stream_import_progress(
21 Path(job_id): Path<uuid::Uuid>,
22 State(state): State<AppState>,
23) -> Result<Sse<impl Stream<Item = Result<Event, Infallible>>>, ApiError> {
24 let watcher = state
25 .imports
26 .subscribe(job_id)
27 .await
28 .ok_or(ApiError::NotFound("import job not found"))?;
29 
30 let stream = WatchStream::new(watcher)
31 .map(|progress| {
32 let terminal = matches!(
33 progress,
34 ImportProgress::Completed { .. } | ImportProgress::Failed { .. }
35 );
36 let event = Event::default()
37 .event("progress")
38 .json_data(&progress)
39 .expect("import progress serializes");
40 (event, terminal)
41 })
42 .take_while_inclusive(|(_, terminal)| !*terminal)
43 .map(|(event, _)| Ok(event));
44 
45 Ok(Sse::new(stream).keep_alive(
46 KeepAlive::new()
47 .interval(Duration::from_secs(15))
48 .text("keep-alive"),
49 ))
50}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1A watch channel plus a stream adapter is a clean way to push live state to clients over SSE.
  2. 2Tagging an enum with serde lets each state variant serialize into a self-describing JSON payload.
  3. 3take_while_inclusive lets you emit the final terminal event and then cleanly close the stream.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Streaming import progress with SSE in Axum — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code