This episode opens up realtime communication: WebSocket with gorilla/websocket inside an Echo handler, Server-Sent Events for one-way notifications, response streaming for large data, and chat, notification, and long-lived connection use cases.

Most APIs work one way: the client asks, the server answers. But there's a world where the server must push data at any time — chat, notifications, live prices. This episode opens up realtime communication in Echo with WebSocket and Server-Sent Events.
Episode 16 covers WebSocket with gorilla/websocket, Server-Sent Events for one-way notifications, response streaming for large data, and chat and long-lived connection use cases.
Echo doesn't ship built-in WebSocket, but integration with gorilla/websocket is seamless. The handler upgrades from plain HTTP:
go get github.com/gorilla/websocketvar upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true },
}
e.GET("/ws", func(c echo.Context) error {
ws, err := upgrader.Upgrade(c.Response(), c.Request(), nil)
if err != nil {
return err
}
defer ws.Close()
for {
msgType, msg, err := ws.ReadMessage()
if err != nil {
return err
}
if err := ws.WriteMessage(msgType, []byte("echo: "+string(msg))); err != nil {
return err
}
}
})This handler echoes every message received from the client. CheckOrigin decides which origins may open a connection — in production, always validate the origin instead of returning true.
Never accept every origin in production. Restrict it to the application's domain:
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return r.Host == "app.example.com"
},
}SSE is a simple way for the server to send updates to the client without requiring a two-way connection. The client just opens a regular HTTP connection:
e.GET("/events", func(c echo.Context) error {
h := c.Response().Header()
h.Set(echo.HeaderContentType, "text/event-stream")
h.Set(echo.HeaderCacheControl, "no-cache")
h.Set(echo.HeaderConnection, "keep-alive")
for i := 1; i <= 10; i++ {
event := "event: notifikasi\n"
event += "data: pesan ke-" + strconv.Itoa(i) + "\n\n"
if _, err := c.Response().Write([]byte(event)); err != nil {
return err
}
c.Response().Flush()
time.Sleep(time.Second)
}
return nil
})The SSE pattern: write an event in the data: <message> format, then call Flush so the data reaches the client immediately. Each event must end with two newlines.
Combine with episode 7: c.Stream and c.Response().Flush allow large data to be sent in stages. SSE is just one form of streaming; other forms include progress reporting and sending large files:
e.GET("/stream", func(c echo.Context) error {
c.Response().Header().Set(echo.HeaderContentType, "text/plain")
for i := 1; i <= 100; i++ {
if _, err := c.Response().Write([]byte("baris " + strconv.Itoa(i) + "\n")); err != nil {
return err
}
c.Response().Flush()
}
return nil
})The client sees the first line long before the server finishes writing the last one — without streaming, the entire response would have to wait until it's complete.
WebSocket shines when two-way communication and low latency are priorities: chat, collaboration, games. The downside: connections are held continuously, so each connection consumes resources and needs vertical scaling or many instances with a centralized broadcast mechanism.
SSE excels for one-way notifications — prices, alerts, feeds — because it's simpler and uses plain HTTP that passes through proxies and load balancers easily.
type Hub struct {
clients map[*websocket.Conn]bool
mu sync.Mutex
}
func (h *Hub) broadcast(msg []byte) {
h.mu.Lock()
defer h.mu.Unlock()
for client := range h.clients {
client.WriteMessage(websocket.TextMessage, msg)
}
}A Hub with a mutex tracks all active connections and spreads messages to all of them. This is a simple form of the publish-subscribe pattern.
Episode 16 opens up realtime communication: WebSocket with gorilla/websocket for two-way dialogue, SSE for one-way push with the data: format, response streaming with Flush, and the hub pattern for broadcasting to many connections.
Key takeaways:
gorilla/websocket inside an Echo handler.CheckOrigin in production; don't accept every origin.text/event-stream and the data: <message> format.Flush sends data immediately before the buffer fills.In episode 17 next, we'll discuss testing & benchmark — unit testing handlers with echo.New() and httptest, testing middleware, mocking services with interfaces, and testing.B benchmarks to measure route allocation and latency.