Replacing frontend polling with SSE for deployment logs

I wanted to push the deployment logs a bit further this time…

Before this, the frontend had to poll the logs endpoint over and over. I tried replacing that approach with SSE so the browser could receive log updates as a stream from the backend instead of repeatedly calling fetch().

From the browser side, this already feels much nicer. The frontend opens one long-lived connection, listens with EventSource, and keeps appending new log lines as they arrive. So in that sense, it looks real-time… but only from browser to backend.

I also recorded a demo for this because the difference is easier to notice from the browser network tab. Before this, the frontend would keep making repeated requests again and again just to get new logs. Now the request pattern already looks different… the browser only opens the log request once, and the backend keeps that connection alive as an EventStream, which you can see directly in DevTools while new messages keep coming in.

Here’s the demo video…

The part that made me pause is that the backend is still not truly event-driven yet. Inside the SSE handler, it still checks the database every 500ms to ask whether there are new logs or not. So the shape right now is more like this:

  • browser ← backend = streaming
  • backend ← database = polling

That is why I see this as only half real-time for now. The streaming part is already there, but under the hood the backend is still polling PostgreSQL in a loop. It works, but I can already see the potential issue… if many users open the logs page later, that means many SSE connections, and each one could keep hitting the database every 500ms just to check for new rows.

This step also made the async nature of the problem much clearer to me. Log delivery is not really a one-shot request/response kind of flow, so a regular function call does not feel like enough for this shape of communication. That is why I intentionally used a channel here, because the communication is asynchronous and needs a way to keep sending values over time.

So even though this is not the final solution, I still like this iteration. It made the frontend experience smoother, and more importantly, it exposed the next bottleneck much more clearly… the real challenge is no longer just how to stream to the browser, but how to avoid repeatedly polling the database underneath.

© 2026 Wahyu Syahputra. All rights reserved.