WebSocket & Real-time
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.
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
})
Related Topicsβ
- Extending Built-in Modules β Wiring custom routes and services
- Hooks & Events - Event-driven updates
Need help? Ask in GitHub Discussions.