Hono with Server Sent Events
Hono is a web framework designed to run on any JavaScript runtime, making it suitable for edge deployments. It can be combined with Server-Sent Events (SSE) to create real-time web applications with a focus on sending server updates to the client.
+--------+ +--------+
| Client |<--->| Server |
+---+----+ +----+---+
| |
| 1. GET /sse |
+-------------->|
| 2. 200 OK |
| (SSE Headers) |
|<--------------+
| 3. Event 1 |
| 'data:...' |
|<--------------+
| 4. Event 2 |
| 'data:...' |
|<--------------+
... ...
Setting up SSE with Hono
1. Setting Headers
For SSE to function correctly, specific headers need to be set. By setting them up inside a middleware, we can ensure they’re consistently applied.
app.use('/sse/*', async (c, next) => {
c.header('Content-Type', 'text/event-stream');
c.header('Cache-Control', 'no-cache');
c.header('Connection', 'keep-alive');
await next();
});
These headers ensure the client maintains an open connection and receives the streamed events as intended.
2. Sending Data with SSE Format
With Hono, we can utilize its streaming feature to start streaming content:
app.get('/sse', (c) => {
return c.stream(async (stream) => {
stream.write('data: hello\n\n');
stream.write('data: world\n\n');
});
});
3. Using Custom Event Types
Beyond basic data, SSE allows us to send custom event types to handle specific client-side actions:
stream.write('event: close\n');
stream.write('data: close\n\n');
4. Adding Event IDs
To keep track of events, especially useful for error recovery:
stream.write('id: 0\n');
5. Setting Retry Intervals
In case of a connection drop, instruct the client when to attempt a reconnection:
stream.write('retry: 1000\n'); // In milliseconds
A Complete Hono-SSE Endpoint Example
import { Hono } from 'hono'
import { cors } from 'hono/cors'
const app = new Hono()
app.use('*', cors(
{
origin: '*',
allowMethods: ['GET'],
allowHeaders: ['Content-Type'],
}
))
app.get('/', (c) => c.text('Hello Hono!'))
app.use('/sse/*', async (c, next) => {
c.header('Content-Type', 'text/event-stream');
c.header('Cache-Control', 'no-cache');
c.header('Connection', 'keep-alive');
await next();
});
app.get('/sse', (c) => {
return c.stream(async (stream) => {
stream.write('retry: 1000\n');
stream.write('id: 0\n');
stream.write('data: hello\n\n');
stream.write('id: 1\n');
stream.write('data: world\n\n');
stream.write('event: close\n');
stream.write('data: close\n\n');
})
});
export default app;
Client-Side Implementation
To use the power of SSE on the client side, a simple listener can be implemented:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>SSE Example</title>
</head>
<body>
<h1>SSE Example</h1>
<div id="sse-data"></div>
<script>
const baseURL = "http://localhost:3000";
const sseData = document.getElementById('sse-data');
const eventSource = new EventSource(`${baseURL}/sse`);
eventSource.onmessage = (event) => {
sseData.innerHTML += event.data + '<br>';
};
eventSource.addEventListener("close", (event) => {
console.log('Received "close" event. Closing connection...');
eventSource.close();
});
eventSource.onerror = (error) => {
console.error('EventSource error:', error);
};
</script>
</body>
</html>
Visit the page in your browser, and watch as SSE seamlessly delivers server updates.
Conclusion
Combining Hono with SSE opens doors to real-time, dynamic web applications.
Consider exploring advanced Hono features and more about SSE:
Code
Receive my stream of work on: