Skip to main content
This guide walks you through building a real-time notification system. Users receive instant notifications via WebSocket.

Prerequisites

  • Go 1.22 or later
  • Basic understanding of Go and JavaScript

Step 1: Create Your Project

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

Create main.go:
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

Create public/index.html:
<!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

go run main.go
Open http://localhost:8080 in your browser.
  1. Enter a user ID (e.g., β€œalice”) and click Connect
  2. Open another tab, connect as β€œbob”
  3. Send a notification from Alice to Bob
  4. Try the broadcast feature

Understanding the Code

Authentication

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

OnMessage: func(ctx context.Context, s *live.Session, topic string, data []byte) {
    // topic: extracted from incoming message
    // data: the message payload
}
Messages from clients are JSON with topic and data fields:
{"topic": "chat", "data": {"text": "Hello"}}

Subscribe and Publish

// Subscribe session to topic
server.Subscribe(s, "user:"+userID)

// Publish to all subscribers
server.Publish("user:"+targetID, data)

Next Steps