Skip to main content

WebSocket & Real-time

DEVELOPER Advanced

Implement real-time features using WebSocket connections.

Overview​

EZ-Console does not ship a first-class WebSocket or SSE module in core. The backend is Gin, so you can add real-time behavior by pulling in libraries such as github.com/gorilla/websocket (or similar) and registering routes like any other controller. The examples below are illustrative patterns; dependency versions and middleware names must match your project.

caution

Built-in AI MCP toolsets may list a websocket protocol in configuration, but WebSocket execution paths in the framework are still evolvingβ€”do not assume full parity with HTTP MCP without checking the version you run.

WebSocket Setup​

Basic WebSocket Handler​

package controller

import (
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
// In production, validate origin
return true
},
}

func (c *WebSocketController) HandleWebSocket(ctx *gin.Context) {
conn, err := upgrader.Upgrade(ctx.Writer, ctx.Request, nil)
if err != nil {
return
}
defer conn.Close()

// Handle WebSocket connection
for {
messageType, message, err := conn.ReadMessage()
if err != nil {
break
}

// Echo message back (example)
err = conn.WriteMessage(messageType, message)
if err != nil {
break
}
}
}

Registering WebSocket Route​

func (c *WebSocketController) RegisterRoutes(ctx context.Context, router *gin.RouterGroup) {
ws := router.Group("/ws")
{
ws.GET("/notifications", middleware.WithAuthentication(c.HandleNotifications))
ws.GET("/chat", middleware.WithAuthentication(c.HandleChat))
}
}

Real-time Notifications​

Notification Service​

package service

import (
"context"
"sync"
"github.com/gorilla/websocket"
)

type NotificationService struct {
connections map[string]*websocket.Conn
mutex sync.RWMutex
}

func NewNotificationService() *NotificationService {
return &NotificationService{
connections: make(map[string]*websocket.Conn),
}
}

func (s *NotificationService) AddConnection(userID string, conn *websocket.Conn) {
s.mutex.Lock()
defer s.mutex.Unlock()
s.connections[userID] = conn
}

func (s *NotificationService) RemoveConnection(userID string) {
s.mutex.Lock()
defer s.mutex.Unlock()
delete(s.connections, userID)
}

func (s *NotificationService) SendToUser(userID string, message []byte) error {
s.mutex.RLock()
conn, exists := s.connections[userID]
s.mutex.RUnlock()

if !exists {
return fmt.Errorf("user not connected")
}

return conn.WriteMessage(websocket.TextMessage, message)
}

func (s *NotificationService) Broadcast(message []byte) {
s.mutex.RLock()
defer s.mutex.RUnlock()

for _, conn := range s.connections {
conn.WriteMessage(websocket.TextMessage, message)
}
}

WebSocket Handler with Notifications​

func (c *WebSocketController) HandleNotifications(ctx *gin.Context) {
conn, err := upgrader.Upgrade(ctx.Writer, ctx.Request, nil)
if err != nil {
return
}
defer conn.Close()

userID, _ := ctx.Get("user_id")
userIDStr := userID.(string)

// Add connection to notification service
c.notificationService.AddConnection(userIDStr, conn)
defer c.notificationService.RemoveConnection(userIDStr)

// Keep connection alive
for {
_, _, err := conn.ReadMessage()
if err != nil {
break
}
}
}

Real-time Chat​

Chat Room Manager​

package service

type ChatRoom struct {
ID string
Connections map[string]*websocket.Conn
mutex sync.RWMutex
}

func NewChatRoom(id string) *ChatRoom {
return &ChatRoom{
ID: id,
Connections: make(map[string]*websocket.Conn),
}
}

func (r *ChatRoom) AddConnection(userID string, conn *websocket.Conn) {
r.mutex.Lock()
defer r.mutex.Unlock()
r.Connections[userID] = conn
}

func (r *ChatRoom) Broadcast(fromUserID string, message []byte) {
r.mutex.RLock()
defer r.mutex.RUnlock()

for userID, conn := range r.Connections {
if userID != fromUserID {
conn.WriteMessage(websocket.TextMessage, message)
}
}
}

Chat WebSocket Handler​

func (c *WebSocketController) HandleChat(ctx *gin.Context) {
conn, err := upgrader.Upgrade(ctx.Writer, ctx.Request, nil)
if err != nil {
return
}
defer conn.Close()

roomID := ctx.Query("room")
userID, _ := ctx.Get("user_id")
userIDStr := userID.(string)

// Get or create chat room
room := c.chatService.GetOrCreateRoom(roomID)
room.AddConnection(userIDStr, conn)

// Handle messages
for {
messageType, message, err := conn.ReadMessage()
if err != nil {
break
}

// Broadcast to room
room.Broadcast(userIDStr, message)
}
}

Frontend WebSocket Client​

React WebSocket Hook​

import { useEffect, useRef, useState } from 'react';

export const useWebSocket = (url: string) => {
const [connected, setConnected] = useState(false);
const [messages, setMessages] = useState<any[]>([]);
const ws = useRef<WebSocket | null>(null);

useEffect(() => {
const token = localStorage.getItem('token');
const wsUrl = `${url}?token=${token}`;

ws.current = new WebSocket(wsUrl);

ws.current.onopen = () => {
setConnected(true);
};

ws.current.onmessage = (event) => {
const message = JSON.parse(event.data);
setMessages(prev => [...prev, message]);
};

ws.current.onerror = (error) => {
console.error('WebSocket error:', error);
};

ws.current.onclose = () => {
setConnected(false);
};

return () => {
ws.current?.close();
};
}, [url]);

const sendMessage = (message: any) => {
if (ws.current && connected) {
ws.current.send(JSON.stringify(message));
}
};

return { connected, messages, sendMessage };
};

Using WebSocket in Component​

export const ChatRoom: React.FC<{ roomId: string }> = ({ roomId }) => {
const { connected, messages, sendMessage } = useWebSocket(`/ws/chat?room=${roomId}`);

const handleSend = (text: string) => {
sendMessage({ type: 'chat', text });
};

return (
<div>
<div>Status: {connected ? 'Connected' : 'Disconnected'}</div>
<div>
{messages.map((msg, i) => (
<div key={i}>{msg.text}</div>
))}
</div>
<input onKeyPress={(e) => {
if (e.key === 'Enter') {
handleSend(e.currentTarget.value);
}
}} />
</div>
);
};

Authentication​

WebSocket Authentication​

func authenticateWebSocket(conn *websocket.Conn) (string, error) {
// Read authentication message
_, authMsg, err := conn.ReadMessage()
if err != nil {
return "", err
}

var auth AuthMessage
json.Unmarshal(authMsg, &auth)

// Validate token
userID, err := validateToken(auth.Token)
if err != nil {
return "", err
}

return userID, nil
}

Token in Query Parameter​

func (c *WebSocketController) HandleNotifications(ctx *gin.Context) {
// Get token from query
token := ctx.Query("token")

// Validate token
userID, err := validateToken(token)
if err != nil {
ctx.AbortWithStatus(http.StatusUnauthorized)
return
}

conn, err := upgrader.Upgrade(ctx.Writer, ctx.Request, nil)
// ...
}

Best Practices​

1. Connection Management​

  • Handle connection errors gracefully
  • Implement reconnection logic
  • Clean up connections on disconnect

2. Message Format​

Use structured message format:

{
"type": "notification",
"data": {
"message": "New product created",
"timestamp": "2024-01-15T10:30:00Z"
}
}

3. Rate Limiting​

Implement rate limiting for WebSocket messages:

var rateLimiter = rate.NewLimiter(rate.Every(time.Second), 10)

if !rateLimiter.Allow() {
// Rate limit exceeded
return
}

4. Ping/Pong​

Keep connections alive with ping/pong:

conn.SetReadDeadline(time.Now().Add(60 * time.Second))
conn.SetPongHandler(func(string) error {
conn.SetReadDeadline(time.Now().Add(60 * time.Second))
return nil
})

Need help? Ask in GitHub Discussions.