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:
Top comments (2)
Great explanation Yanael.
Would you elaborate on how to implement the "Channel"? like express "sse-pubsub"
Thank you! Iβm not familiar with
sse-pubsub
, but from what I see, itβs like a wrapper around functions and properly formatted requests.You could create a class with a constructor to set up the stream, where you can configure headers, the retry parameter, and other settings. Then, you can add functions to send data in the correct format, so you donβt have to worry about things like id, data, and newlines, just handle those details inside a function.
For publishing and subscribing, it seems to manage a list of clients. A subscribe function could add clients to the list, and a publish function could send messages to all of them.
Youβll also need functions to close and stop the connection.
If you try building this, Iβd be happy to take a look.