This episode covers WebSocket in Fiber: upgrading the connection with the github.com/gofiber/contrib/websocket package, two-way handlers based on *websocket.Conn, reading JSON messages, ping/pong and close handlers, and integration with browser clients.

In episode 13 you streamed events from server to client with SSE. Episode 14 completes it with WebSocket — realtime two-way communication where both client and server can send messages at any time.
WebSocket fits chat, games, realtime collaboration, and state synchronization. In Fiber, it's implemented with the github.com/gofiber/contrib/websocket package, which wraps the gorilla/websocket library and integrates seamlessly with Fiber routes.
Before using WebSocket, the server must verify that the client actually sent an upgrade request. Fiber provides websocket.IsWebSocketUpgrade:
import "github.com/gofiber/contrib/websocket"
app.Use("/ws", func(c fiber.Ctx) error {
if websocket.IsWebSocketUpgrade(c) {
return c.Next()
}
return fiber.ErrUpgradeRequired
})This middleware is attached to the /ws prefix. If the request contains the WebSocket upgrade headers, it's passed to the next handler; if not, it returns status 426 Upgrade Required. This prevents the WebSocket endpoint from being called as plain HTTP.
A WebSocket handler receives *websocket.Conn, not fiber.Ctx. This differs from ordinary handlers:
app.Get("/ws/:id", websocket.New(func(c *websocket.Conn) {
var msg Message
for {
if err := c.ReadJSON(&msg); err != nil {
c.WriteMessage(websocket.TextMessage,
[]byte("format salah atau koneksi tertutup"))
continue
}
c.WriteJSON(fiber.Map{"echo": msg.Text})
}
}))websocket.New(handler) registers the connection handler. c.ReadJSON(&msg) waits for the next message and parses JSON; c.WriteJSON(data) sends a reply. The loop runs while the connection is open — when the client closes it, ReadJSON returns an error and the loop stops.
Not every message is text. Use ReadMessage to check the type — TextMessage, BinaryMessage, PingMessage, and others:
app.Get("/ws", websocket.New(func(c *websocket.Conn) {
for {
msgType, msg, err := c.ReadMessage()
if err != nil {
return
}
if msgType == websocket.BinaryMessage {
handleBinary(msg)
} else {
handleText(string(msg))
}
}
}))c.ReadMessage() returns the type and the raw payload. By checking the type, one connection can handle text and binary data at once — for example JSON metadata plus a binary file.
An idle WebSocket connection can be dropped by a proxy. Fiber uses PingHandler and PongHandler to keep connections alive:
app.Get("/ws", websocket.New(func(c *websocket.Conn) {
c.SetPingHandler(func(appData string) error {
return c.WriteControl(websocket.PongMessage,
[]byte(appData), time.Now().Add(time.Second))
})
c.SetCloseHandler(func(code int, text string) error {
log.Printf("client menutup: %d %s", code, text)
return c.WriteControl(websocket.CloseMessage,
websocket.FormatCloseMessage(code, ""), time.Now().Add(time.Second))
})
for {
if _, _, err := c.ReadMessage(); err != nil {
return
}
}
}))SetPingHandler answers a ping with a pong; SetCloseHandler records the client's close code. websocket.FormatCloseMessage(code, text) builds a standard close message. These handlers ensure the connection ends cleanly without hanging.
In the browser, WebSocket is also native, with no libraries:
<script>
const ws = new WebSocket("ws://localhost:3000/ws/1");
ws.onopen = () => ws.send(JSON.stringify({ text: "halo" }));
ws.onmessage = (e) => console.log("server:", e.data);
ws.onclose = () => console.log("koneksi tertutup");
</script>new WebSocket("ws://localhost:3000/ws/1") opens the connection; onopen sends the first message, onmessage receives the echo from the server, and onclose marks the connection closed. Note the ws:// protocol (not http://) — for HTTPS connections use wss://.
curl --include --no-buffer --header \
"Connection: Upgrade" --header "Upgrade: websocket" \
--header "Sec-WebSocket-Key: SGVsbG8=" --header "Sec-WebSocket-Version: 13" \
http://localhost:3000/ws/1The curl request with upgrade headers returns 101 Switching Protocols — proof the upgrade succeeded. For an interactive test, use a browser: open a page loading the WebSocket client above and send messages from the console.
Key takeaways:
websocket.IsWebSocketUpgrade(c) verifies an upgrade request; if not, return fiber.ErrUpgradeRequired.*websocket.Conn and are registered with websocket.New.ReadJSON/WriteJSON for JSON messages; ReadMessage to check message types.SetPingHandler/SetPongHandler keep connections alive.SetCloseHandler and FormatCloseMessage handle connection closing cleanly.WebSocket with the ws:// or wss:// scheme.In the next episode, episode 15, we discuss hooks and event-driven in v3 — registering listeners for Fiber events, emitting custom events, and using them for application instrumentation.