DEV Community

Cover image for Hono with Server Sent Events πŸ”₯πŸ’Œ
Yanael
Yanael

Posted on

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:...'    |
      |<--------------+
     ...             ...
Enter fullscreen mode Exit fullscreen mode

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();
});
Enter fullscreen mode Exit fullscreen mode

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');
    });
});
Enter fullscreen mode Exit fullscreen mode

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');
Enter fullscreen mode Exit fullscreen mode

4. Adding Event IDs

To keep track of events, especially useful for error recovery:

stream.write('id: 0\n');
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
tbee profile image
T-Bee

Great explanation Yanael.
Would you elaborate on how to implement the "Channel"? like express "sse-pubsub"

Collapse
 
yanael profile image
Yanael

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.