The Streaming helper provides utilities for sending streaming responses, including regular data streams, Server-Sent Events (SSE), and text streams.
Import
import { stream, streamSSE, streamText } from 'hono/streaming'
Basic Streaming
The stream helper creates a streaming response for sending data progressively.
Simple Stream
import { stream } from 'hono/streaming'
app.get('/stream', (c) => {
return stream(c, async (stream) => {
// Write data chunks
for (let i = 0; i < 5; i++) {
await stream.write(new Uint8Array([i]))
await stream.sleep(100) // Wait 100ms between chunks
}
})
})
Writing Different Data Types
import { stream } from 'hono/streaming'
app.get('/data', (c) => {
return stream(c, async (stream) => {
// Write string
await stream.write('Hello ')
// Write Uint8Array
await stream.write(new Uint8Array([72, 105])) // "Hi"
// Write with new line
await stream.writeln('World')
})
})
Error Handling
Handle errors during streaming:
import { stream } from 'hono/streaming'
app.get('/stream-with-error', (c) => {
return stream(
c,
async (stream) => {
await stream.write('Starting...')
throw new Error('Something went wrong!')
},
async (err, stream) => {
// Error handler
console.error('Stream error:', err)
await stream.write('Error occurred')
}
)
})
Abort Handling
Detect when the client disconnects:
import { stream } from 'hono/streaming'
app.get('/long-stream', (c) => {
return stream(c, async (stream) => {
let stopped = false
stream.onAbort(() => {
console.log('Client disconnected')
stopped = true
})
while (!stopped) {
await stream.write('data...')
await stream.sleep(1000)
}
})
})
Pipe from ReadableStream
Pipe data from another stream:
import { stream } from 'hono/streaming'
app.get('/pipe', (c) => {
return stream(c, async (stream) => {
const response = await fetch('https://example.com/data')
if (response.body) {
await stream.pipe(response.body)
}
})
})
Server-Sent Events (SSE)
The streamSSE helper provides real-time server-to-client communication using Server-Sent Events.
Basic SSE
import { streamSSE } from 'hono/streaming'
app.get('/sse', (c) => {
return streamSSE(c, async (stream) => {
let id = 0
while (true) {
await stream.writeSSE({
data: `Message ${id}`,
event: 'update',
id: String(id++),
})
await stream.sleep(1000)
}
})
})
import { streamSSE, SSEMessage } from 'hono/streaming'
app.get('/sse-advanced', (c) => {
return streamSSE(c, async (stream) => {
// Send message with all fields
await stream.writeSSE({
data: 'Message content',
event: 'custom-event',
id: '123',
retry: 5000, // Reconnection time in ms
})
})
})
data
string | Promise<string>
required
The message data. Can contain newlines.
Custom event type (default is “message”)
Event ID for the Last-Event-ID header
Reconnection time in milliseconds
Client-Side SSE
Connect to SSE endpoint from the browser:
const eventSource = new EventSource('/sse')
// Listen to default message event
eventSource.onmessage = (event) => {
console.log('Message:', event.data)
}
// Listen to custom event
eventSource.addEventListener('update', (event) => {
console.log('Update:', event.data)
})
// Handle connection open
eventSource.onopen = () => {
console.log('Connection opened')
}
// Handle errors
eventSource.onerror = (error) => {
console.error('SSE error:', error)
}
// Close connection
eventSource.close()
SSE with Error Handling
import { streamSSE } from 'hono/streaming'
app.get('/sse-safe', (c) => {
return streamSSE(
c,
async (stream) => {
await stream.writeSSE({
data: 'Starting...',
})
throw new Error('Processing failed')
},
async (err, stream) => {
// Error handler sends error event
await stream.writeSSE({
event: 'error',
data: err.message,
})
}
)
})
SSE responses automatically set appropriate headers:
Transfer-Encoding: chunked
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
Text Streaming
The streamText helper is optimized for streaming text responses.
Basic Text Stream
import { streamText } from 'hono/streaming'
app.get('/text-stream', (c) => {
return streamText(c, async (stream) => {
const words = ['Hello', 'World', 'from', 'Hono']
for (const word of words) {
await stream.write(word + ' ')
await stream.sleep(200)
}
})
})
Streaming LLM Responses
import { streamText } from 'hono/streaming'
app.get('/ai-stream', (c) => {
return streamText(c, async (stream) => {
// Simulate streaming from an LLM
const response = 'This is a simulated AI response'
for (const char of response) {
await stream.write(char)
await stream.sleep(50) // Simulate typing effect
}
})
})
Text streams automatically set:
Content-Type: text/plain
X-Content-Type-Options: nosniff
Transfer-Encoding: chunked
StreamingAPI Methods
All streaming helpers provide a StreamingApi instance with these methods:
write()
Write data to the stream:
await stream.write('text')
await stream.write(new Uint8Array([1, 2, 3]))
writeln()
Write data with a newline:
await stream.writeln('Line 1')
await stream.writeln('Line 2')
sleep()
Pause for a specified duration:
await stream.sleep(1000) // Wait 1 second
pipe()
Pipe from a ReadableStream:
await stream.pipe(readableStream)
onAbort()
Register an abort handler:
stream.onAbort(() => {
console.log('Stream aborted')
})
close()
Manually close the stream:
await stream.write('Final message')
stream.close()
Use Cases
Real-time Updates
Use SSE for live notifications, dashboards, and real-time data feeds
AI Streaming
Stream LLM responses for better user experience with text streaming
File Downloads
Stream large files without loading them entirely into memory
Log Streaming
Stream server logs or process output in real-time
Best Practices
Always handle client disconnections with onAbort() to clean up resources and stop processing.
SSE is one-way communication (server to client). For bidirectional communication, use WebSockets instead.
Add appropriate error handlers to prevent unhandled promise rejections and ensure graceful error recovery.
Browser Compatibility
SSE is supported in all modern browsers:
- Chrome/Edge: Full support
- Firefox: Full support
- Safari: Full support
- Opera: Full support
SSE does not work in Internet Explorer. Consider using WebSockets for broader compatibility.