| name | koapp-sse |
| description | Stream Server-Sent Events (SSE) from a @axiosleo/koapp Koa route using the KoaSSEMiddleware. Use when pushing real-time events to a browser over HTTP, implementing progress bars, live logs, notifications, or EventSource endpoints with text/event-stream, including ctx.koa.sse.send/close/keepAlive APIs. |
@axiosleo/koapp Server-Sent Events Middleware
Source: src/middlewares/sse.js.
KoaSSEMiddleware upgrades a Koa request to a Server-Sent Events (SSE)
stream, setting the right headers and exposing ctx.sse.send(...) for
pushing events to the browser over plain HTTP.
Use SSE when:
- You need server → client streaming (notifications, logs, live charts)
- You do NOT need client → server streaming (use koapp-apps WebSocket instead)
- You want a simple, proxy-friendly transport - SSE works over HTTP/1.1
Browsers reconnect automatically; most reverse proxies handle it fine.
Import
const { KoaSSEMiddleware } = require('@axiosleo/koapp').middlewares;
Middleware factory
const middleware = KoaSSEMiddleware({
pingInterval: 60000,
closeEvent: 'close'
});
The factory returns a standard Koa middleware (ctx, next) => {}.
Wiring into a router handler
Because the framework already dispatches routes via its workflow, invoke
the middleware inside the handler, passing context.koa:
const { Router } = require('@axiosleo/koapp');
const { KoaSSEMiddleware } = require('@axiosleo/koapp').middlewares;
const router = new Router('/');
router.any('/sse', async (context) => {
const sse = KoaSSEMiddleware();
await sse(context.koa, async () => {});
context.koa.sse.send({ data: 'hello, world!' });
process.nextTick(async () => {
for (let i = 0; i < 5; i++) {
context.koa.sse.send({ id: i, event: 'tick', data: { i } });
await new Promise((r) => setTimeout(r, 1000));
}
context.koa.sse.close();
});
});
Key points:
- Call
await sse(context.koa, async () => {}) - pass a no-op next.
- After that,
context.koa.sse exists.
- Push events with
context.koa.sse.send(event); end with .close().
- Do not call
success / failed / result in the same handler - SSE
takes over the response body.
Event shape
sse.send(data) accepts either a string or an object:
context.koa.sse.send('hello');
context.koa.sse.send({
id: 42,
event: 'progress',
data: { percent: 75 }
});
Raw wire output:
id:42
event:progress
data:{"percent":75}
Lifecycle methods
| Method | When to use |
|---|
ctx.sse.send(data) | Push one event |
ctx.sse.keepAlive() | Send a :\n\n comment to keep the connection warm (called automatically every pingInterval ms for all active streams) |
ctx.sse.close() | Emit a final { event: closeEvent } and end the stream |
The middleware maintains an internal ssePool and calls keepAlive on
every registered stream every pingInterval ms. Manual calls are rarely
needed.
Reacting to client disconnect
The middleware listens to close / error events on the SSE stream and
calls an internal close() that:
- Removes the stream from the pool
unpipe() + destroy() the stream
- Ends the Koa response
- Destroys the underlying socket
You can listen yourself too:
context.koa.sse.on('close', () => {
clearInterval(myPushLoop);
});
Long-lived handlers
The handler function must remain alive while the SSE stream is open.
Don't return from it immediately; either keep pushing inline, or spawn a
background task and let it own the lifecycle:
router.any('/logs', async (context) => {
const sse = KoaSSEMiddleware();
await sse(context.koa, async () => {});
(async () => {
for await (const line of tail('./app.log')) {
context.koa.sse.send({ event: 'log', data: line });
}
context.koa.sse.close();
})();
});
The framework's workflow will see the handler resolve, but the response
body (the SSE stream) remains open until you close() it or the client
disconnects.
Browser client
const source = new EventSource('/sse');
source.onmessage = (e) => {
console.log('data:', e.data);
};
source.addEventListener('progress', (e) => {
const payload = JSON.parse(e.data);
console.log('progress:', payload.percent);
});
source.addEventListener('close', () => {
console.log('server closed the stream');
source.close();
});
Browsers automatically reconnect on network blips.
Pairing with other koapp pieces
- Validate the upgrade with a router-level validator on
query
(e.g. auth token) - koapp-router
- Authenticate via session cookie - the middleware does not touch auth
- Control backpressure by only pushing when
context.koa.sse.writable
Common pitfalls
See also
- Real-time browser apps needing bi-directional traffic → koapp-apps (WebSocket)
- Progress indicators for long-running HTTP jobs → this skill +
Workflow from @axiosleo/cli-tool
- Concrete SSE recipes: examples.md