Yanaƫl Barbier

I build my path with an export!

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: