Skip to main content
This guide walks you through creating a simple offline-first application using the sync package. You’ll learn how to set up a client, create collections, and use reactive primitives.

Prerequisites

  • Go 1.22 or later
  • Basic understanding of Go

Step 1: Create Your Project

mkdir sync-demo && cd sync-demo
go mod init sync-demo
go get github.com/go-mizu/mizu

Step 2: Define Your Data Type

Create main.go:
package main

import (
    "context"
    "fmt"
    "time"

    "github.com/go-mizu/mizu/view/sync"
)

// Todo is our synchronized data type
type Todo struct {
    Title     string `json:"title"`
    Completed bool   `json:"completed"`
}

Step 3: Create and Configure the Client

func main() {
    // Create the sync client
    client := sync.New(sync.Options{
        BaseURL: "http://localhost:8080/_sync",  // Your sync server
        Scope:   "user:demo",                     // Data partition

        // Optional callbacks
        OnSync: func(cursor uint64) {
            fmt.Printf("Synced to cursor %d\n", cursor)
        },
        OnOnline: func() {
            fmt.Println("Connected to server")
        },
        OnOffline: func() {
            fmt.Println("Working offline")
        },
        OnError: func(err error) {
            fmt.Printf("Error: %v\n", err)
        },
    })

    // ... continue below
}

Step 4: Start the Client

    // Start sync in the background
    ctx := context.Background()
    if err := client.Start(ctx); err != nil {
        fmt.Printf("Initial sync failed: %v\n", err)
        // Client continues working offline
    }
    defer client.Stop()

Step 5: Create a Collection

    // Create a typed collection
    todos := sync.NewCollection[Todo](client, "todo")

Step 6: Use the Collection

    // Create a todo (works even offline!)
    todo := todos.Create("todo-1", Todo{
        Title:     "Learn sync",
        Completed: false,
    })

    // Read the value
    fmt.Printf("Created: %s\n", todo.Get().Title)

    // Update it
    todo.Set(Todo{
        Title:     "Learn sync",
        Completed: true,
    })

    // List all todos
    fmt.Printf("\nAll todos (%d):\n", todos.Count())
    for _, entity := range todos.All() {
        t := entity.Get()
        status := "[ ]"
        if t.Completed {
            status = "[x]"
        }
        fmt.Printf("  %s %s\n", status, t.Title)
    }

Step 7: Add Reactivity

    // React to changes automatically
    sync.NewEffect(func() {
        count := todos.Count()
        fmt.Printf("\n>>> Todo count changed: %d\n", count)
    })

    // Create more todos
    todos.Create("todo-2", Todo{Title: "Build app"})
    todos.Create("todo-3", Todo{Title: "Deploy"})

    // Delete one
    todos.Get("todo-2").Delete()

Complete Example

package main

import (
    "context"
    "fmt"
    "time"

    "github.com/go-mizu/mizu/view/sync"
)

type Todo struct {
    Title     string `json:"title"`
    Completed bool   `json:"completed"`
}

func main() {
    // Create client
    client := sync.New(sync.Options{
        BaseURL: "http://localhost:8080/_sync",
        Scope:   "user:demo",
        OnSync: func(cursor uint64) {
            fmt.Printf("Synced to cursor %d\n", cursor)
        },
        OnOnline: func() {
            fmt.Println("Connected!")
        },
        OnOffline: func() {
            fmt.Println("Offline - changes will sync later")
        },
    })

    // Start sync
    ctx := context.Background()
    client.Start(ctx)
    defer client.Stop()

    // Create collection
    todos := sync.NewCollection[Todo](client, "todo")

    // Log changes reactively
    sync.NewEffect(func() {
        fmt.Printf("Todo count: %d\n", todos.Count())
    })

    // Create some todos
    todos.Create("1", Todo{Title: "Learn Go"})
    todos.Create("2", Todo{Title: "Build sync app"})
    todos.Create("3", Todo{Title: "Deploy"})

    // Mark one complete
    entity := todos.Get("1")
    todo := entity.Get()
    todo.Completed = true
    entity.Set(todo)

    // Find incomplete
    incomplete := todos.Find(func(t Todo) bool {
        return !t.Completed
    })
    fmt.Printf("\nIncomplete todos:\n")
    for _, e := range incomplete {
        fmt.Printf("  - %s\n", e.Get().Title)
    }

    // Keep running to see sync happen
    time.Sleep(3 * time.Second)
}

Running the Example

If you have a sync server running:
go run main.go
Output:
Connected!
Synced to cursor 0
Todo count: 0
Todo count: 1
Todo count: 2
Todo count: 3

Incomplete todos:
  - Build sync app
  - Deploy
Synced to cursor 3

What Just Happened?

1. Client connects to server
   │
   â–¼
2. Initial sync (pulls any existing data)
   │
   â–¼
3. Collections created (bound to client)
   │
   â–¼
4. Effects set up (react to changes)
   │
   â–¼
5. Local changes made (immediate)
   │
   â–¼
6. Mutations queued (background)
   │
   â–¼
7. Push to server (when online)
   │
   â–¼
8. OnSync callback (confirms sync)
Key points:
  • Changes are immediate locally
  • Sync happens in the background
  • Works offline - mutations queue until connected
  • Reactive - effects run automatically when data changes

Understanding the Parts

Client

The client manages:
  • Connection to sync server
  • Mutation queue (for offline support)
  • Pulling changes from server
  • Online/offline state

Collection

A collection is a reactive set of entities:
  • Type-safe with generics
  • Syncs with server automatically
  • Supports CRUD operations
  • Reactive (triggers effects)

Entity

An entity is a single record:
  • Has a unique ID
  • Reactive Get/Set
  • Can be deleted
  • Tracks existence

Reactive Primitives

  • Signal: A value that notifies when changed
  • Computed: A derived value that auto-updates
  • Effect: A function that runs when dependencies change

Next Steps