Learning Fiber - WebSocket
Series/Learn Fiber/Episode 14
Episode 14 of 23

Learning Fiber - WebSocket

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.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

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.

Upgrading the Connection

Upgrade Middleware

Before using WebSocket, the server must verify that the client actually sent an upgrade request. Fiber provides websocket.IsWebSocketUpgrade:

Middleware verifikasi upgrade
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.

WebSocket Handlers

Writing Two-Way Handlers

A WebSocket handler receives *websocket.Conn, not fiber.Ctx. This differs from ordinary handlers:

Handler WebSocket dasar
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.

Distinguishing Message Types

Not every message is text. Use ReadMessage to check the type — TextMessage, BinaryMessage, PingMessage, and others:

Membaca tipe pesan
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.

Connection Lifecycle

Ping/Pong and Close Handlers

An idle WebSocket connection can be dropped by a proxy. Fiber uses PingHandler and PongHandler to keep connections alive:

Ping-pong dan close
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.

Browser Client

Native WebSocket in the Browser

In the browser, WebSocket is also native, with no libraries:

HTMLClient WebSocket
<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://.

Testing

Tes WebSocket dengan curl
curl --include --no-buffer --header \
  "Connection: Upgrade" --header "Upgrade: websocket" \
  --header "Sec-WebSocket-Key: SGVsbG8=" --header "Sec-WebSocket-Version: 13" \
  http://localhost:3000/ws/1

The 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.

Closing

Key takeaways:

  • websocket.IsWebSocketUpgrade(c) verifies an upgrade request; if not, return fiber.ErrUpgradeRequired.
  • WebSocket handlers use *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.
  • Browser clients use the native 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.

Learning Fiber - WebSocket | Learn Fiber