The core of building a real-time chat application using Go and WebSockets is to realize full-duplex communication and concurrent management; 2. First, build a WebSocket server through the gorilla/websocket package to handle connection upgrades and messaging; 3. Create a front-end HTML page, use JavaScript to establish a WebSocket connection, realize message sending and real-time display; 4. Introduce Hub structure to centrally manage client connections, broadcast messages to all clients through the broadcast channel, and realize multi-user chat; 5. In the production environment, security needs to be enhanced, including verifying Origin, adding authentication mechanisms, using structured message formats, implementing current limits and enabling TLS; 6. The final application is based on a simple concurrency model, with good scalability, and can further support chat rooms, message persistence and other functions, and fully realize the entire process from development to deployment.

Building a real-time chat application with Go and WebSockets is a great way to learn about full-duplex communication, concurrency, and how to structure a backend service efficiently. Go's simplicity, strong standard library, and excellent support for concurrency make it a perfect choice for this kind of application. Here's how you can build one step by step.

1. Setting Up the WebSocket Server in Go
Go doesn't include WebSocket support in the standard library, but the most popular and reliable package is gorilla/websocket
. First, initialize your module and install the package:
go mod init chat-app
go get github.com/gorilla/websocket
Now, set up a basic HTTP server that upgrades HTTP connections to WebSocket:

// main.go
package main
import (
"log"
"net/http"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true // Allow all origins (adjust in production)
},
}
func handleConnections(w http.ResponseWriter, r *http.Request) {
// Upgrade HTTP to WebSocket
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Fatal(err)
Return
}
defer conn.Close()
// Simple echo loop
for {
var msg string
err := conn.ReadJSON(&msg)
if err != nil {
log.Printf("Read error: %v", err)
break
}
log.Printf("Received: %s", msg)
// Echo back the message
if err := conn.WriteJSON(msg); err != nil {
log.Printf("Write error: %v", err)
break
}
}
}
func main() {
http.HandleFunc("/ws", handleConnections)
http.Handle("/", http.FileServer(http.Dir("./public")))
log.Println("Server started on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
This sets up a basic WebSocket endpoint at /ws
and serves static files from a public
directory.
2. Creating the Frontend with HTML and JavaScript
Create a simple public/index.html
file:

<!DOCTYPE html>
<html>
<head>
<title>Go Chat</title>
</head>
<body>
<h2>Real-Time Chat</h2>
<ul id="messages"></ul>
<input id="input" type="text" placeholder="Type a message..." />
<button onclick="sendMessage()">Send</button>
<script>
const ws = new WebSocket("ws://localhost:8080/ws");
const messages = document.getElementById("messages");
const input = document.getElementById("input");
ws.onmessage = function(event) {
const li = document.createElement("li");
li.textContent = event.data;
messages.appendChild(li);
};
function sendMessage() {
if (input.value) {
ws.send(input.value);
input.value = "";
}
}
input.addEventListener("keypress", function(e) {
if (e.key === "Enter") sendMessage();
});
</script>
</body>
</html>
This frontend connects to the WebSocket, displays incoming messages, and sends user input.
3. Managing Multiple Clients with a Hub
To enable chat between multiple users, you need a central hub to manage connections and broadcast messages.
// hub.go
package main
import (
"sync"
)
type Hub struct {
clients map[*Client]bool
broadcast chan []byte
register chan *Client
unregister chan *Client
mutex sync.RWMutex
}
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.mutex.Lock()
h.clients[client] = true
h.mutex.Unlock()
case client := <-h.unregister:
h.mutex.Lock()
if _, ok := h.clients[client]; ok {
delete(h.clients, client)
close(client.send)
}
h.mutex.Unlock()
case message := <-h.broadcast:
h.mutex.RLock()
for client := range h.clients {
select {
case client.send <- message:
default:
close(client.send)
delete(h.clients, client)
}
}
h.mutex.RUnlock()
}
}
}
Now, define the Client
struct and modify handleConnections
to work with the hub:
type Client struct {
hub *Hub
conn *websocket.Conn
send chan []byte
}
func handleConnections(hub *Hub, w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Fatal(err)
Return
}
client := &Client{hub: hub, conn: conn, send: make(chan []byte, 256)}
client.hub.register <- client
defer func() {
client.hub.unregister <- client
}()
go client.writePump()
client.readPump()
}
func (c *Client) readPump() {
defer func() {
c.hub.unregister <- c
c.conn.Close()
}()
for {
_, message, err := c.conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
log.Printf("Read error: %v", err)
}
break
}
c.hub.broadcast <- message
}
}
func (c *Client) writePump() {
defer func() {
c.conn.Close()
}()
for message := range c.send {
err := c.conn.WriteMessage(websocket.TextMessage, message)
if err != nil {
log.Printf("Write error: %v", err)
break
}
}
}
Update main()
to use the hub:
func main() {
hub := NewHub()
go hub.Run()
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
handleConnections(hub, w, r)
})
http.Handle("/", http.FileServer(http.Dir("./public")))
log.Println("Server started on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
4. Security and Production Considerations
While this app works for development, here are a few things to improve before going live:
Origin checking : Update CheckOrigin
to allow only trusted domains.
Authentication : Add JWT or session-based auth to identify users.
Message structure : Use a structured format like:
type Message struct {
Username string `json:"username"`
Content string `json:"content"`
Time string `json:"time"`
}
Rate limiting : Prevent spam by limiting message frequency per client.
TLS : Use wss://
by serving over HTTPS in production.
Final Notes
With Go and WebSockets, you've built a working real-time chat app using minimal dependencies and clean concurrency patterns. The hub pattern is widely used and scales well for modern loads. You can extend this further with rooms, persistent messages, or even integrate with a frontend framework like React.
Basically, it's simple to get started — but powerful enough to grow.
The above is the detailed content of Building a Real-Time Chat Application with Go and WebSockets. For more information, please follow other related articles on the PHP Chinese website!