Core Guides
Stable v1.4.8
WebSocket Support
Learn about WebSocket Support in EchoNext.
WebSocket Support
EchoNext provides type-safe WebSocket handlers with connection management and broadcasting capabilities.
Basic Usage
import (
"github.com/abdussamadbello/echonext"
"github.com/abdussamadbello/echonext/websocket"
)
func chatHandler(conn *websocket.Connection) error {
for {
// Read message
var msg ChatMessage
if err := conn.ReadJSON(&msg); err != nil {
return err // Connection closed
}
// Process and respond
response := ChatResponse{
Text: "Echo: " + msg.Text,
Timestamp: time.Now(),
}
if err := conn.WriteJSON(response); err != nil {
return err
}
}
}
func main() {
app := echonext.New()
app.WS("/chat", chatHandler)
app.Start(":8080")
}Connection Type
The websocket.Connection type provides:
type Connection struct {
ID string // Unique connection ID
Metadata map[string]interface{} // Custom metadata storage
}
// Methods
func (c *Connection) ReadJSON(v interface{}) error // Read JSON message
func (c *Connection) WriteJSON(v interface{}) error // Write JSON message
func (c *Connection) ReadMessage() (int, []byte, error) // Read raw message
func (c *Connection) WriteMessage(t int, data []byte) error // Write raw message
func (c *Connection) Close() error // Close connection
// Metadata
func (c *Connection) SetMetadata(key string, value interface{})
func (c *Connection) GetMetadata(key string) interface{}Hub Pattern
Use the Hub pattern for managing multiple connections and broadcasting:
import "github.com/abdussamadbello/echonext/websocket"
type ChatHandler struct {
hub *websocket.Hub
}
func NewChatHandler() *ChatHandler {
hub := websocket.NewHub()
go hub.Run() // Start hub in background
return &ChatHandler{hub: hub}
}
func (h *ChatHandler) OnConnect(conn *websocket.Connection) error {
// Store user info
conn.SetMetadata("username", "anonymous")
// Register with hub
h.hub.Register(conn)
// Broadcast join message
h.hub.Broadcast(Message{
Type: "join",
Text: "A user joined the chat",
})
return nil
}
func (h *ChatHandler) OnMessage(conn *websocket.Connection, msgType int, data []byte) error {
var msg ChatMessage
if err := json.Unmarshal(data, &msg); err != nil {
return err
}
// Broadcast to all connections
response := Message{
Type: "message",
Username: conn.GetMetadata("username").(string),
Text: msg.Text,
Time: time.Now(),
}
return h.hub.Broadcast(response)
}
func (h *ChatHandler) OnDisconnect(conn *websocket.Connection, err error) {
h.hub.Unregister(conn)
h.hub.Broadcast(Message{
Type: "leave",
Text: "A user left the chat",
})
}
// Usage
handler := NewChatHandler()
app.WS("/ws/chat", handler)Hub Methods
type Hub struct {
// ...
}
func NewHub() *Hub // Create new hub
func (h *Hub) Run() // Start hub (run in goroutine)
func (h *Hub) Register(conn *Connection) // Add connection
func (h *Hub) Unregister(conn *Connection) // Remove connection
func (h *Hub) Broadcast(msg interface{}) error // Send to all connections
func (h *Hub) BroadcastRaw(data []byte) error // Send raw bytes to all
func (h *Hub) Count() int // Get connection count
func (h *Hub) GetConnection(id string) *Connection // Get specific connection
func (h *Hub) ForEach(fn func(*Connection)) // Iterate all connectionsHandler Interface
Implement the full handler interface for complete control:
type WSHandler interface {
OnConnect(conn *Connection) error
OnMessage(conn *Connection, messageType int, data []byte) error
OnDisconnect(conn *Connection, err error)
}Or use a simple function:
// Simple function handler
app.WS("/echo", func(conn *websocket.Connection) error {
for {
msgType, data, err := conn.ReadMessage()
if err != nil {
return err
}
if err := conn.WriteMessage(msgType, data); err != nil {
return err
}
}
})Message Types
Define structured message types:
type Message struct {
Type string `json:"type"`
Payload interface{} `json:"payload"`
Timestamp time.Time `json:"timestamp"`
}
type ChatMessage struct {
Text string `json:"text"`
}
type UserJoinedPayload struct {
Username string `json:"username"`
UserID string `json:"user_id"`
}Authentication
Authenticate WebSocket connections:
func (h *ChatHandler) OnConnect(conn *websocket.Connection) error {
// Get echo context from upgrade request
// Authentication should happen before upgrade
// Store authenticated user
conn.SetMetadata("user_id", "user123")
conn.SetMetadata("username", "john")
h.hub.Register(conn)
return nil
}Room/Channel Support
Implement rooms using multiple hubs or connection metadata:
type RoomManager struct {
rooms map[string]*websocket.Hub
mu sync.RWMutex
}
func (rm *RoomManager) GetOrCreateRoom(roomID string) *websocket.Hub {
rm.mu.Lock()
defer rm.mu.Unlock()
if hub, exists := rm.rooms[roomID]; exists {
return hub
}
hub := websocket.NewHub()
go hub.Run()
rm.rooms[roomID] = hub
return hub
}
func (h *ChatHandler) OnConnect(conn *websocket.Connection) error {
roomID := conn.GetMetadata("room_id").(string)
room := h.roomManager.GetOrCreateRoom(roomID)
room.Register(conn)
return nil
}Error Handling
func (h *ChatHandler) OnMessage(conn *websocket.Connection, msgType int, data []byte) error {
var msg Message
if err := json.Unmarshal(data, &msg); err != nil {
// Send error back to client
conn.WriteJSON(Message{
Type: "error",
Error: "Invalid message format",
})
return nil // Don't disconnect on parse errors
}
switch msg.Type {
case "chat":
return h.handleChat(conn, msg)
case "ping":
return conn.WriteJSON(Message{Type: "pong"})
default:
conn.WriteJSON(Message{
Type: "error",
Error: "Unknown message type",
})
return nil
}
}Client-Side JavaScript
const ws = new WebSocket('ws://localhost:8080/ws/chat');
ws.onopen = () => {
console.log('Connected');
ws.send(JSON.stringify({
type: 'chat',
text: 'Hello!'
}));
};
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
console.log('Received:', msg);
};
ws.onclose = () => {
console.log('Disconnected');
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};CLI Generator
Generate WebSocket handler boilerplate:
echonext generate websocket chatThis creates:
internal/ws/chat/
├── handler.go # WebSocket connection handler
├── hub.go # Connection management and broadcasting
└── message.go # Message types and serializationBest Practices
- Use the Hub pattern - Centralize connection management
- Handle disconnects gracefully - Clean up resources in OnDisconnect
- Implement heartbeats - Keep connections alive with ping/pong
- Rate limit messages - Prevent abuse from rapid message sending
- Validate all input - Never trust client messages
- Use JSON for messages - Structured data is easier to handle
- Log connection events - Track connects/disconnects for debugging
Heartbeat Example
func (h *ChatHandler) OnConnect(conn *websocket.Connection) error {
h.hub.Register(conn)
// Start heartbeat
go func() {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for range ticker.C {
if err := conn.WriteJSON(Message{Type: "ping"}); err != nil {
return
}
}
}()
return nil
}Example Project
See examples/websocket-demo/ for a complete working example with:
- Real-time chat application
- Hub pattern implementation
- HTML client interface
- Message broadcasting
- Connection management