Introduction to WebSocket in Go: Why Real-Time?
Modern web applications increasingly require instant data delivery. The traditional HTTP request/response model no longer suffices for tasks like chats, online games, stock quotes, and monitoring dashboards. This is where WebSocket comes to the rescue — a protocol that provides a persistent bidirectional connection between client and server. In this article, we will explore how to build real-time applications in Go (Golang) using WebSocket, from setup to production-ready solutions.
Go is ideal for WebSocket thanks to its lightweight concurrency model (goroutines) and high performance. We will progress from a simple echo server to a full-fledged chat and monitoring system.
WebSocket Basics and Environment Setup
What is WebSocket?
WebSocket is a protocol that operates over TCP. It starts with an HTTP handshake (upgrade), after which the connection switches to a persistent bidirectional channel. Unlike HTTP, the server can send data to the client without a request — this is the foundation of real-time communication.
Installation and Libraries
Go does not have a standard library for WebSocket, but there is a popular library gorilla/websocket. Let's install it:
go get github.com/gorilla/websocketYou can also use nhooyr.io/websocket (a more modern alternative), but in this article we will stick with gorilla as the most widely used.
First WebSocket Server in Go
Let's write a simple echo server that receives a message and sends it back:
package main
import ( "log" "net/http" "github.com/gorilla/websocket")
var upgrader = websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true // for development },}
func echoHandler(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { log.Print("Upgrade:", err) return } defer conn.Close()
for { mt, message, err := conn.ReadMessage() if err != nil { log.Println("Read:", err) break } log.Printf("Received: %s", message) err = conn.WriteMessage(mt, message) if err != nil { log.Println("Write:", err) break } }}
func main() { http.HandleFunc("/echo", echoHandler) log.Println("Server started on :8080") log.Fatal(http.ListenAndServe(":8080", nil))}This is a minimal working example. Note the Upgrader — it checks the HTTP headers and switches the connection.
Real-Time Application Architecture: Chat in Go
Connection Management
For a chat, you need to manage multiple clients. Let's create a Hub structure that will store all active connections and broadcast messages:
type Client struct { hub *Hub conn *websocket.Conn send chan []byte}
type Hub struct { clients map[*Client]bool broadcast chan []byte register chan *Client unregister chan *Client}
func newHub() *Hub { return &Hub{ clients: make(map[*Client]bool), broadcast: make(chan []byte), register: make(chan *Client), unregister: make(chan *Client), }}
func (h *Hub) run() { for { select { case client := <-h.register: h.clients[client] = true case client := <-h.unregister: if _, ok := h.clients[client]; ok { delete(h.clients, client) close(client.send) } case message := <-h.broadcast: for client := range h.clients { select { case client.send <- message: default: