Prerequisites
- Go 1.22 or later
- Basic understanding of Go and JavaScript
Step 1: Create Your Project
Copy
mkdir live-demo && cd live-demo
go mod init live-demo
go get github.com/go-mizu/mizu
mkdir public
Step 2: Create the Server
Createmain.go:
Copy
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"github.com/go-mizu/mizu"
"github.com/go-mizu/mizu/live"
)
var server *live.Server
func main() {
// Create the live server
server = live.New(live.Options{
// Authenticate connections
OnAuth: func(ctx context.Context, r *http.Request) (any, error) {
userID := r.URL.Query().Get("user")
if userID == "" {
return nil, fmt.Errorf("user parameter required")
}
return userID, nil
},
// Handle incoming messages
OnMessage: func(ctx context.Context, s *live.Session, topic string, data []byte) {
userID := s.Value().(string)
log.Printf("Message from %s: topic=%s", userID, topic)
switch topic {
case "subscribe":
// Client wants to subscribe to a topic
var req struct{ Topic string `json:"topic"` }
json.Unmarshal(data, &req)
server.Subscribe(s, req.Topic)
log.Printf("User %s subscribed to %s", userID, req.Topic)
case "broadcast":
// Broadcast to all on global topic
server.Publish("global", data)
}
},
// Handle disconnections
OnClose: func(s *live.Session, err error) {
userID := s.Value().(string)
log.Printf("User %s disconnected: %v", userID, err)
},
})
// Create Mizu app
app := mizu.New()
// WebSocket endpoint
app.Get("/ws", mizu.Compat(server.Handler()))
// API to send notifications
app.Post("/api/notify", notifyHandler)
app.Post("/api/broadcast", broadcastHandler)
// Serve static files
app.Static("/", "./public")
log.Println("Server running on http://localhost:8080")
app.Listen(":8080")
}
func notifyHandler(c *mizu.Ctx) error {
var req struct {
UserID string `json:"user_id"`
Title string `json:"title"`
Message string `json:"message"`
}
if err := c.BodyJSON(&req); err != nil {
return c.Status(400).JSON(map[string]string{"error": "invalid request"})
}
// Publish to user's personal topic
data, _ := json.Marshal(map[string]string{
"title": req.Title,
"message": req.Message,
})
server.Publish("user:"+req.UserID, data)
return c.JSON(map[string]string{"status": "sent"})
}
func broadcastHandler(c *mizu.Ctx) error {
var req struct {
Title string `json:"title"`
Message string `json:"message"`
}
if err := c.BodyJSON(&req); err != nil {
return c.Status(400).JSON(map[string]string{"error": "invalid request"})
}
// Publish to global topic
data, _ := json.Marshal(map[string]string{
"title": req.Title,
"message": req.Message,
})
server.Publish("global", data)
return c.JSON(map[string]string{"status": "broadcast sent"})
}
Step 3: Create the Frontend
Createpublic/index.html:
Copy
<!DOCTYPE html>
<html>
<head>
<title>Live Notifications</title>
<style>
body { font-family: sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; }
.status { padding: 10px; border-radius: 5px; margin-bottom: 20px; }
.connected { background: #d4edda; color: #155724; }
.disconnected { background: #f8d7da; color: #721c24; }
.notification { padding: 15px; margin: 10px 0; border-radius: 5px; background: #e3f2fd; border-left: 4px solid #2196f3; animation: slideIn 0.3s; }
@keyframes slideIn { from { opacity: 0; transform: translateX(-20px); } to { opacity: 1; transform: translateX(0); } }
input, button { padding: 10px; margin: 5px; }
button { cursor: pointer; background: #2196f3; color: white; border: none; border-radius: 5px; }
</style>
</head>
<body>
<h1>Live Notifications</h1>
<div id="status" class="status disconnected">Disconnected</div>
<div>
<input type="text" id="userId" placeholder="Your user ID">
<button onclick="connect()">Connect</button>
</div>
<h2>Notifications</h2>
<div id="notifications"></div>
<h2>Send Test</h2>
<div>
<input type="text" id="targetUser" placeholder="Target user ID">
<input type="text" id="message" placeholder="Message">
<button onclick="sendNotify()">Send to User</button>
<button onclick="sendBroadcast()">Broadcast All</button>
</div>
<script>
let ws = null;
function connect() {
const userId = document.getElementById('userId').value;
if (!userId) { alert('Enter user ID'); return; }
if (ws) ws.close();
ws = new WebSocket(`ws://${location.host}/ws?user=${userId}`);
ws.onopen = () => {
document.getElementById('status').textContent = `Connected as: ${userId}`;
document.getElementById('status').className = 'status connected';
// Subscribe to personal and global topics
ws.send(JSON.stringify({topic: 'subscribe', data: {topic: 'user:' + userId}}));
ws.send(JSON.stringify({topic: 'subscribe', data: {topic: 'global'}}));
};
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
addNotification(msg.topic, JSON.parse(msg.data));
};
ws.onclose = () => {
document.getElementById('status').textContent = 'Disconnected';
document.getElementById('status').className = 'status disconnected';
};
}
function addNotification(topic, data) {
const div = document.createElement('div');
div.className = 'notification';
div.innerHTML = `<strong>[${topic}] ${data.title}</strong><br>${data.message}`;
document.getElementById('notifications').prepend(div);
}
async function sendNotify() {
const targetUser = document.getElementById('targetUser').value;
const message = document.getElementById('message').value;
await fetch('/api/notify', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({user_id: targetUser, title: 'Notification', message})
});
}
async function sendBroadcast() {
const message = document.getElementById('message').value;
await fetch('/api/broadcast', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({title: 'Broadcast', message})
});
}
</script>
</body>
</html>
Step 4: Run and Test
Copy
go run main.go
http://localhost:8080 in your browser.
- Enter a user ID (e.g., βaliceβ) and click Connect
- Open another tab, connect as βbobβ
- Send a notification from Alice to Bob
- Try the broadcast feature
Understanding the Code
Authentication
Copy
OnAuth: func(ctx context.Context, r *http.Request) (any, error) {
userID := r.URL.Query().Get("user")
if userID == "" {
return nil, fmt.Errorf("user required")
}
return userID, nil // Value stored in session
}
OnAuth returns:
(value, nil)- Accept connection, store value in session(nil, error)- Reject connection
Message Handling
Copy
OnMessage: func(ctx context.Context, s *live.Session, topic string, data []byte) {
// topic: extracted from incoming message
// data: the message payload
}
topic and data fields:
Copy
{"topic": "chat", "data": {"text": "Hello"}}
Subscribe and Publish
Copy
// Subscribe session to topic
server.Subscribe(s, "user:"+userID)
// Publish to all subscribers
server.Publish("user:"+targetID, data)