Skip to content

Streaming Responses

Stratal provides three streaming methods on RouterContext, each tailored to a different content type. All three follow the same callback pattern and support optional error handling.

Use ctx.stream() to send raw binary or encoded data. You are responsible for encoding the content before writing.

import { Controller, Route, type RouterContext } from 'stratal/router'
@Controller('/data')
class DataController {
@Route()
show(ctx: RouterContext) {
return ctx.stream(async (stream) => {
await stream.write(new TextEncoder().encode('chunk 1'))
await stream.write(new TextEncoder().encode('chunk 2'))
await stream.close()
})
}
}

Use ctx.streamText() for plain text responses. This method automatically sets the Content-Encoding: Identity header, which is required for streaming to work on Cloudflare Workers.

@Route()
show(ctx: RouterContext) {
return ctx.streamText(async (stream) => {
await stream.write('Hello ')
await stream.write('World')
await stream.close()
})
}

This is particularly useful for streaming AI model responses token by token.

Use ctx.streamSSE() to send an SSE stream. The correct Content-Type and Content-Encoding headers are set automatically.

@Route()
index(ctx: RouterContext) {
return ctx.streamSSE(async (stream) => {
for (let i = 0; i < 5; i++) {
await stream.writeSSE({
event: 'message',
data: JSON.stringify({ count: i }),
id: String(i),
})
await stream.sleep(1000)
}
})
}

The writeSSE() method accepts an object with the following fields:

FieldTypeDescription
eventstringThe event name the client listens for.
datastringThe event payload. Serialize objects with JSON.stringify().
idstringOptional event ID for client reconnection tracking.

The stream.sleep() helper pauses for the given number of milliseconds, which is handy for throttling or simulating intervals.

All three streaming methods accept an optional second argument — an error callback that is invoked if the stream callback throws.

ctx.streamSSE(
async (stream) => {
// stream logic...
},
async (err) => {
console.error('Stream error:', err)
}
)

Without an error handler, exceptions inside the stream callback will be silently swallowed after the response headers have already been sent.