---
title: "21 — Realtime Communication — WebSockets, Melody, and Centrifugo"
uid: realtime-communication
tags: ["golang", "centrifugo", "websocket", "concurrency", "roadmap:golang", "melody", "realtime", "sse"]
excerpt: "One cheap goroutine per connection makes Go an unusually good fit for realtime — from raw WebSockets up to Melody (minimal wrapper) and Centrifugo (full messaging server)."
date: 2026-08-13T03:28:08+0000
source: https://www.aveshina.my.id/en/blog/realtime-communication
---

Chat apps, live dashboards, collaborative tools — anything where the server pushes updates without being asked. The model that clicked: **Go's concurrency model — one cheap goroutine per connection, with channels coordinating them — makes it an unusually good fit for realtime, and the ecosystem offers three layers: raw WebSockets for full control, Melody as a minimalist wrapper that handles the boilerplate, and Centrifugo as a full-featured messaging server when you need presence, history, and scale.** [1] The reason Go shines here is structural: a server holding 50,000 open WebSocket connections is holding 50,000 goroutines, which Go handles comfortably where a thread-per-connection model would collapse.

## Why Go fits realtime

Realtime servers are fundamentally about many long-lived concurrent connections. Every connected client is an open socket that the server must read from, write to, and coordinate with all the others — broadcasting a chat message to everyone in a room, pushing a price update to every subscriber of a stock. Two Go properties make this cheap [1]:

- **Goroutine-per-connection.** Each connected client gets its own goroutine reading its socket, costing kilobytes rather than megabytes. 50,000 connected clients is a normal load for a Go realtime server on modest hardware.
- **Channels for coordination.** Broadcasting a message to all connections is a fan-out pattern (covered in the concurrency notes) — send the message to a channel that every connection's goroutine reads from. The coordination primitive is built in.

The result is that realtime in Go is not an exotic specialty; it is the concurrency model applied to long-lived connections.

## WebSockets — the transport

WebSockets are the standard transport for bidirectional realtime in the browser — a single upgraded HTTP connection that stays open and allows both client and server to push frames at any time [2]. Unlike HTTP request/response, a WebSocket is persistent and either side can initiate a message. This is what powers chat, multiplayer cursors, and live trading UIs.

Go's standard library does not include a WebSocket package (it predates the WebSocket standard's stability), so the ecosystem standard is gorilla/websocket or the newer nhooyr.io/websocket (now coder/websocket). Both expose the same core loop: upgrade the HTTP connection, then read and write frames in a per-connection goroutine. The server fans out messages by ranging over all live connections and writing to each.

The lower-level alternative is **Server-Sent Events (SSE)** — a one-way server-to-client stream over plain HTTP. SSE is simpler (no protocol upgrade, works through more proxies) and is enough for dashboards and notification feeds where the client only needs to _receive_ pushes. WebSockets are the choice when the client also needs to send frequent messages upstream.

## Melody — the minimalist wrapper

Writing raw WebSocket code gets repetitive fast: upgrade the connection, register it in a connection set, broadcast on each message, handle disconnects and pings. **Melody** is a minimalist framework that wraps this pattern with a clean API [3]:

```
m := melody.New()
m.HandleConnect(func(s *melody.Session) { /* a client joined */ })
m.HandleDisconnect(func(s *melody.Session) { /* a client left */ })
m.HandleMessage(func(s *melody.Session, msg []byte) {
    m.Broadcast(msg)   // send to every connected session
})
```

Melody handles session management, ping/pong keepalive, message broadcasting, and rooms (session groups). It integrates cleanly with an existing net/http or framework-based server. For a chat app, a notification broadcaster, or any realtime feature where you want the structure without writing the plumbing, Melody is the productive middle layer — more than raw WebSockets, far less than a full messaging server.

## Centrifugo — the messaging server

When the realtime needs outgrow what a library can provide — presence (who is online), message history (replay missed messages), per-channel permissions, horizontal scaling across multiple server nodes, and multi-protocol clients (WebSocket, SSE, HTTP streaming) — the answer is a dedicated messaging server. **Centrifugo** is the Go-native option [4]:

- **Channels and subscriptions** — clients subscribe to named channels; the server routes published messages to all subscribers.
- **Presence and history** — the server tracks who is in a channel and retains recent messages so reconnecting clients can catch up.
- **Scalability** — Centrifugo can be scaled horizontally with Redis as a backing store, so multiple server nodes share the pub/sub load.
- **Protocol flexibility** — the same backend speaks WebSocket, SSE, and HTTP streaming, so clients pick the transport that works for them.

The architecture is that your Go application publishes events to Centrifugo (over its API or a Redis-backed broker), and Centrifugo manages the fan-out to all connected clients. This separates the realtime fan-out concern from your application logic — your service does not hold the client connections at all, Centrifugo does. For a system where realtime is central and the connection count is large, Centrifugo (or a similar server like NATS) is what lets the application servers stay stateless.

## How I use this

The choice maps to scale and feature needs. For a single feature with a few hundred connections — a live notification bell, a document-coordination cursor — Melody on top of the existing HTTP server is the sweet spot; it adds the broadcast plumbing without a new infrastructure component. For raw control or an unusual protocol, the underlying WebSocket libraries are fine to use directly. Centrifugo enters the picture when the realtime layer is genuinely a product surface — a chat platform, a live-trading dashboard — where presence, history, and multi-node scale are requirements, not nice-to-haves. In every case the underlying reason Go handles it well is the same: a goroutine per connection, a channel to coordinate, and the whole fan-out is just the concurrency patterns from the earlier notes applied to long-lived sockets. That uniformity is why a Go backend is often the default recommendation for realtime.

## References

[1] The Go Authors, "net/http package," pkg.go.dev, 2024. [Online]. Available: [https://pkg.go.dev/net/http](https://pkg.go.dev/net/http)

[2] Wisemonks, "Implementing WebSockets in Golang," Medium, 2024. [Online]. Available: [https://medium.com/wisemonks/implementing-websockets-in-golang-d3e8e219733b](https://medium.com/wisemonks/implementing-websockets-in-golang-d3e8e219733b)

[3] O. Hong, "olahol/melody: Minimalist websocket framework," GitHub, 2024. [Online]. Available: [https://github.com/olahol/melody](https://github.com/olahol/melody)

[4] Centrifugal, "Centrifugo," centrifugal.dev, 2024. [Online]. Available: [https://centrifugal.dev/](https://centrifugal.dev/)

```quiz
Q: Why is Go well-suited for realtime servers holding tens of thousands of connections?
- Go compiles WebSocket support into every binary
- the goroutine-per-connection model means each open socket costs kilobytes, not megabytes, and channels coordinate fan-out broadcasts naturally
correct: 1
explain: A thread-per-connection model would exhaust memory at this scale. Go's cheap goroutines and channels make holding 50k connections and broadcasting to them a normal workload.

Q: What does Melody provide over raw WebSocket libraries?
- a new binary transport protocol
- session management, ping/pong keepalive, message broadcasting, and rooms — the boilerplate you would otherwise write by hand
correct: 1
explain: Melody wraps the common WebSocket server pattern: connection registration, broadcast, disconnect handling. It is the productive middle layer for chat and notification features.

Q: When does Centrifugo become the right choice over a library like Melody?
- when you need any realtime feature at all
- when you need presence, message history, multi-node scale, or multi-protocol clients that a library cannot provide
correct: 1
explain: Centrifugo is a dedicated messaging server with channels, presence, history, and Redis-backed horizontal scaling. It separates the realtime fan-out from your application, which a library cannot do at scale.

Q: Server-Sent Events (SSE) differ from WebSockets in that…
- SSE is faster and bidirectional
- SSE is a one-way server-to-client stream over plain HTTP, simpler but only server-pushed; WebSockets are bidirectional after an upgrade
correct: 1
explain: SSE works for dashboards and feeds where the client only receives. WebSockets are needed when the client also sends frequent messages upstream. SSE's simplicity (plain HTTP, proxy-friendly) is its advantage.

Q: Broadcasting a chat message to every connection in a Go server is an instance of which concurrency pattern?
- the pipeline pattern
- fan-out — one message is distributed to many connection goroutines, typically via a channel each reads from
correct: 1
explain: Fan-out distributes one input across many workers (here, connection goroutines). The concurrency patterns from earlier notes apply directly to realtime fan-out.
```
