Real-Time Messaging in Go with Gin and Gorilla WebSocket
This guide shows how to add WebSocket endpoints to a Gin-based Go service using Gorillla WebSocket, plus a minimal browser client to exercise both text and JSON message flows.
WebSocket libray
The server uses Gorilla WebSocket to the protocol upgrade and message I/O:
- github.com/gorilla/websocket
Backend (Go)
The server exposes two WebSocket routes:
- /ws/text: accepts a single text message and responds 10 times with an incrementing suffix
- /ws/json: accepts a single JSON object and responds 10 times with the same fields plus a counter
Static files are served from ./public with index.html as the fallback.
package main
import (
"fmt"
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
// Adjust for your deployment; this disables origin checks
return true
},
}
func handleText(c *gin.Context) {
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
log.Printf("upgrade failed: %v", err)
return
}
defer conn.Close()
msgType, payload, err := conn.ReadMessage()
if err != nil {
log.Printf("read failed: %v", err)
return
}
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for i := 1; i <= 10; i++ {
<-ticker.C
resp := fmt.Sprintf("%s %d", string(payload), i)
if err := conn.WriteMessage(msgType, []byte(resp)); err != nil {
log.Printf("write failed: %v", err)
return
}
}
}
func handleJSON(c *gin.Context) {
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
log.Printf("upgrade failed: %v", err)
return
}
defer conn.Close()
type inMsg struct {
A string `json:"a"`
B int `json:"b"`
}
var req inMsg
if err := conn.ReadJSON(&req); err != nil {
log.Printf("read json failed: %v", err)
return
}
type outMsg struct {
A string `json:"a"`
B int `json:"b"`
C int `json:"c"`
}
for i := 1; i <= 10; i++ {
resp := outMsg{A: req.A, B: req.B, C: i}
if err := conn.WriteJSON(resp); err != nil {
log.Printf("write json failed: %v", err)
return
}
time.Sleep(1 * time.Second)
}
}
func main() {
r := gin.Default()
r.GET("/ws/text", handleText)
r.GET("/ws/json", handleJSON)
// Static files from ./public (place index.html here)
r.Static("/", "./public")
r.NoRoute(func(c *gin.Context) {
c.File("./public/index.html")
})
if err := r.Run(":8000"); err != nil {
log.Fatal(err)
}
}
Place the following HTML at public/index.html.
Frontend (HTML)
The page opens two WebSocket connections on load, sends one message to each, and renders the replies as they arrive.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>WebSocket Demo</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<h1>WebSocket test</h1>
<h2>JSON stream</h2>
<pre id="json-log"></pre>
<h2>Text stream</h2>
<pre id="text-log"></pre>
<script>
document.addEventListener('DOMContentLoaded', () => {
const scheme = location.protocol === 'https:' ? 'wss' : 'ws';
const base = `${scheme}://${location.host}`;
// JSON endpoint
const j = new WebSocket(`${base}/ws/json`);
j.addEventListener('open', () => {
j.send(JSON.stringify({ a: 'bb', b: 2 }));
});
j.addEventListener('message', (evt) => {
document.getElementById('json-log').textContent += evt.data + '\n';
});
// Text endpoint
const t = new WebSocket(`${base}/ws/text`);
t.addEventListener('open', () => {
t.send('text message');
});
t.addEventListener('message', (evt) => {
document.getElementById('text-log').textContent += evt.data + '\n';
});
});
</script>
</body>
</html>
Run the Go server and open http://localhsot:8000 in your browser. Each sent message results in 10 rseponses spaced one second apart.