Go SDK

The official NoLag SDK for Go. Idiomatic Go client with goroutines and channels support.

Installation

go get github.com/NoLagApp/nolag-go

Quick Start

package main

import (
    "fmt"
    "time"

    nolag "github.com/NoLagApp/nolag-go"
)

func main() {
    // Create client with your actor token
    client := nolag.New("your-actor-token")

    // Connect to NoLag
    if err := client.Connect(); err != nil {
        panic(err)
    }
    defer client.Close()

    // Subscribe to a topic
    err := client.Subscribe("my-topic", func(data any, meta nolag.MessageMeta) {
        fmt.Printf("Received: %v\n", data)
    })
    if err != nil {
        panic(err)
    }

    // Publish a message
    if err := client.Emit("my-topic", map[string]any{"hello": "world"}); err != nil {
        fmt.Printf("Emit failed: %v\n", err)
    }

    // Get actor ID assigned by server
    fmt.Println("Actor ID:", client.ActorID())

    // Keep running
    time.Sleep(60 * time.Second)
}

Configuration

import (
    "time"
    nolag "github.com/NoLagApp/nolag-go"
)

options := nolag.Options{
    URL:                  "wss://broker.nolag.app/ws", // Custom broker URL
    Reconnect:            true,                         // Auto-reconnect (default: true)
    ReconnectInterval:    5 * time.Second,              // Reconnect interval (default: 5s)
    MaxReconnectAttempts: 10,                           // Max attempts, 0 = infinite (default: 10)
    HeartbeatInterval:    30 * time.Second,             // Heartbeat interval, 0 to disable (default: 30s)
    LoadBalance:          true,                         // Enable load balancing (default: false)
    LoadBalanceGroup:     "workers",                    // Load balance group name
    ActorTokenID:         "custom-id",                  // Optional actor token identifier
    Debug:                true,                         // Enable debug logging (default: false)
}

client := nolag.New("your-actor-token", options)

Subscribing to Topics

Subscribe, Unsubscribe, and all filter methods return error. Always check the returned error.

// Basic subscription — Subscribe returns an error
err := client.Subscribe("chat/messages", func(data any, meta nolag.MessageMeta) {
    fmt.Printf("Message from %s: %v\n", meta.Sender, data)
})
if err != nil {
    fmt.Printf("Subscribe failed: %v\n", err)
}

// With options (load balancing + filters)
loadBalance := true
err = client.Subscribe("tasks", handler, nolag.SubscribeOptions{
    LoadBalance:      &loadBalance,
    LoadBalanceGroup: "workers",
    Filters:          []string{"priority:high", "region:us"},
})
if err != nil {
    fmt.Printf("Subscribe failed: %v\n", err)
}

// Unsubscribe — also returns an error
if err := client.Unsubscribe("chat/messages"); err != nil {
    fmt.Printf("Unsubscribe failed: %v\n", err)
}

Publishing Messages

Emit returns an error. Use EmitOptions to set retain, echo, and filter targeting.

// Publish any data (maps, structs, strings, etc.) — Emit returns an error
if err := client.Emit("chat/messages", map[string]any{"text": "Hello!"}); err != nil {
    fmt.Printf("Emit failed: %v\n", err)
}

// With options
echo := false
err := client.Emit("status", map[string]any{"online": true}, nolag.EmitOptions{
    Retain: true,     // Retain last message for new subscribers
    Echo:   &echo,    // Don't receive this message back (default: true)
    Filter: "room-1", // Target specific filter subscribers
})
if err != nil {
    fmt.Printf("Emit failed: %v\n", err)
}

Fluent API (SetApp / SetRoom)

The fluent API scopes all operations to an app/room pair. Topics are automatically prefixed, so room.Emit("messages", ...) publishes to "chat/general/messages".

// The fluent API scopes operations to an app/room.
// Topics are automatically prefixed with "app/room/".
room := client.SetApp("chat").SetRoom("general")

// Subscribe — topic becomes "chat/general/messages"
err := room.Subscribe("messages", func(data any, meta nolag.MessageMeta) {
    fmt.Printf("Message: %v\n", data)
})
if err != nil {
    fmt.Printf("Subscribe failed: %v\n", err)
}

// Emit — topic becomes "chat/general/messages"
if err := room.Emit("messages", map[string]any{"text": "Hello!"}); err != nil {
    fmt.Printf("Emit failed: %v\n", err)
}

// Unsubscribe
room.Unsubscribe("messages")

// Filter management on a room
room.SetFilters("messages", []string{"priority:high"})
room.AddFilters("messages", []string{"priority:medium"})
room.RemoveFilters("messages", []string{"priority:high"})

// Event handlers on scoped topics
room.On("messages", func(args ...any) {
    fmt.Println("Custom event on chat/general/messages")
})
room.Off("messages")

// Get the full topic prefix
fmt.Println(room.Prefix()) // "chat/general"

Filter Management

Filters narrow which messages a subscriber receives. You can set filters at subscribe time or manage them dynamically with SetFilters, AddFilters, and RemoveFilters. On the publish side, use EmitOptions.Filter to target specific subscribers.

// Subscribe with initial filters
err := client.Subscribe("orders", handler, nolag.SubscribeOptions{
    Filters: []string{"region:us", "status:pending"},
})

// Replace all filters for a topic (empty slice = receive all messages)
err = client.SetFilters("orders", []string{"region:eu", "status:shipped"})

// Add filters to existing set (deduplicates automatically)
err = client.AddFilters("orders", []string{"status:delivered"})

// Remove specific filters
err = client.RemoveFilters("orders", []string{"status:shipped"})

// Emit with a filter value — only subscribers with matching filter receive it
err = client.Emit("orders", orderData, nolag.EmitOptions{
    Filter: "region:us",
})

Connection Events

// Listen for connection events
client.On("connected", func(args ...any) {
    fmt.Println("Connected!")
})

client.On("disconnected", func(args ...any) {
    fmt.Println("Disconnected")
})

client.On("reconnecting", func(args ...any) {
    attempt := args[0].(int)
    fmt.Printf("Reconnecting... attempt %d\n", attempt)
})

client.On("error", func(args ...any) {
    err := args[0].(error)
    fmt.Printf("Error: %v\n", err)
})

client.On("presence", func(args ...any) {
    topic := args[0].(string)
    data := args[1]
    fmt.Printf("Presence update on %s: %v\n", topic, data)
})

// Remove all handlers for an event
client.Off("error")

// Check connection status
if client.Status() == nolag.StatusConnected {
    fmt.Println("We're connected!")
}

// Get the actor ID assigned by the server after authentication
fmt.Println("Actor ID:", client.ActorID())

Presence

// Set your presence data
if err := client.SetPresence(map[string]any{
    "status": "online",
    "typing": false,
}); err != nil {
    fmt.Printf("SetPresence failed: %v\n", err)
}

// Get presence of all actors in a topic
presenceList, err := client.GetPresence("chat/room-1")
if err == nil {
    for _, actor := range presenceList {
        fmt.Printf("%s (%s): %v (joined %s)\n",
            actor.ActorTokenID,
            actor.ActorType,
            actor.Presence,
            actor.JoinedAt,
        )
    }
}

// Listen for presence changes
client.On("presence", func(args ...any) {
    topic := args[0].(string)
    presence := args[1]
    fmt.Printf("Presence update in %s: %v\n", topic, presence)
})

Error Handling

package main

import (
    "errors"
    "fmt"

    nolag "github.com/NoLagApp/nolag-go"
)

func main() {
    client := nolag.New("your-actor-token")

    // Handle errors via event
    client.On("error", func(args ...any) {
        if err, ok := args[0].(error); ok {
            fmt.Printf("Error: %v\n", err)
        }
    })

    // Connect with error handling
    if err := client.Connect(); err != nil {
        fmt.Printf("Connection failed: %v\n", err)
        return
    }
    defer client.Close()

    // All operations return errors
    if err := client.Subscribe("topic", handler); err != nil {
        // Check for specific error types
        if errors.Is(err, nolag.ErrNotConnected) {
            fmt.Println("Not connected!")
        }
        fmt.Printf("Subscribe failed: %v\n", err)
    }

    if err := client.Emit("topic", "data"); err != nil {
        fmt.Printf("Emit failed: %v\n", err)
    }

    if err := client.Unsubscribe("topic"); err != nil {
        fmt.Printf("Unsubscribe failed: %v\n", err)
    }

    if err := client.SetFilters("topic", []string{"filter1"}); err != nil {
        fmt.Printf("SetFilters failed: %v\n", err)
    }
}

// Sentinel errors available:
// nolag.ErrNotConnected — operation attempted while disconnected
// nolag.ErrAuthFailed   — authentication failed
// nolag.ErrTimeout      — operation timed out

REST API Client

The SDK also includes a REST API client for managing apps, rooms, and actors:

package main

import (
    "context"
    "fmt"

    nolag "github.com/NoLagApp/nolag-go"
)

func main() {
    ctx := context.Background()

    // Create API client with project-scoped API key
    api := nolag.NewAPI("nlg_live_xxx.secret")

    // List all apps in your project
    apps, err := api.Apps.List(ctx, nil)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Found %d apps\n", len(apps.Data))

    // Create a new app
    app, err := api.Apps.Create(ctx, nolag.AppCreate{
        Name:        "my-chat-app",
        Description: "A real-time chat application",
    })
    if err != nil {
        panic(err)
    }
    fmt.Printf("Created app: %s\n", app.AppID)

    // Create a room in the app
    room, err := api.Rooms.Create(ctx, app.AppID, nolag.RoomCreate{
        Name: "general",
        Slug: "general",
    })
    if err != nil {
        panic(err)
    }
    fmt.Printf("Created room: %s\n", room.RoomID)

    // Create an actor (save the access token!)
    actor, err := api.Actors.Create(ctx, nolag.ActorCreate{
        Name:      "web-client",
        ActorType: nolag.ActorDevice,
    })
    if err != nil {
        panic(err)
    }
    fmt.Printf("Actor token: %s\n", actor.AccessToken)
}

Load Balancing

Distribute messages across multiple subscribers:

// Enable load balancing per-subscription
loadBalance := true
err := client.Subscribe("tasks", processTask, nolag.SubscribeOptions{
    LoadBalance:      &loadBalance,
    LoadBalanceGroup: "task-workers",
})
if err != nil {
    fmt.Printf("Subscribe failed: %v\n", err)
}

// Or enable load balancing globally via connection options
client := nolag.New("your-actor-token", nolag.Options{
    LoadBalance:      true,
    LoadBalanceGroup: "task-workers",
})

Type Definitions

import nolag "github.com/NoLagApp/nolag-go"

// WebSocket Client
nolag.Client           // The real-time messaging client
nolag.Options          // Connection options (URL, Reconnect, LoadBalance, etc.)
nolag.SubscribeOptions // Subscription options (LoadBalance, Filters, etc.)
nolag.EmitOptions      // Publish options (Retain, Echo, Filter)
nolag.App              // Intermediate context from SetApp()
nolag.Room             // Scoped pub/sub context from SetApp().SetRoom()

// Enums / Constants
nolag.ConnectionStatus // StatusDisconnected, StatusConnecting, StatusConnected, StatusReconnecting
nolag.ActorType        // ActorDevice, ActorUser, ActorServer, ActorSession
nolag.QoS              // QoSAtMostOnce, QoSAtLeastOnce, QoSExactlyOnce

// Sentinel Errors
nolag.ErrNotConnected  // Not connected to broker
nolag.ErrAuthFailed    // Authentication failed
nolag.ErrTimeout       // Operation timed out

// Data types
nolag.MessageMeta      // Message metadata (Sender, Timestamp, IsReplay, MsgID, Filter)
nolag.ActorPresence    // Presence info (ActorTokenID, ActorType, Presence, JoinedAt)
nolag.MessageHandler   // func(data any, meta MessageMeta)
nolag.EventHandler     // func(args ...any)

// REST API Client
nolag.API              // REST API client
nolag.APIOptions       // API client options
nolag.NoLagAPIError    // API error type
nolag.APIError         // Raw API error details

// Resources
nolag.AppResource, nolag.AppCreate, nolag.AppUpdate
nolag.RoomResource, nolag.RoomCreate, nolag.RoomUpdate
nolag.ActorResource, nolag.ActorWithToken, nolag.ActorCreate, nolag.ActorUpdate
nolag.PaginatedApps, nolag.ListOptions

// WebRTC (uses pion/webrtc/v3)
nolag.WebRTCManager, nolag.WebRTCOptions, nolag.PeerState
nolag.WebRTCEvent, nolag.WebRTCEventHandler, nolag.TrackHandler

Requirements

  • Go 1.21+
  • github.com/gorilla/websocket v1.5.1
  • github.com/vmihailenco/msgpack/v5 v5.4.1
  • github.com/pion/webrtc/v3 v3.2.50 (WebRTC support)

Next Steps