Documentation Index Fetch the complete documentation index at: https://docs.go-mizu.dev/llms.txt
Use this file to discover all available pages before exploring further.
The sync template combines view rendering, live updates, and data synchronization. The key component is the “sync engine” which tracks changes as operations, replays them to rebuild state, and broadcasts updates to all connected clients. This is how collaborative apps like Google Docs work.
Directory Layout
myapp/
├── cmd/server/main.go # Entry point
├── app/server/
│ ├── app.go # App + sync engine setup
│ ├── config.go # Configuration
│ └── routes.go # HTTP + sync routes
├── handler/
│ └── home.go # Home page handler
├── service/
│ └── todo/
│ └── mutator.go # Todo store and apply logic
├── assets/
│ ├── embed.go # Asset embedding
│ ├── css/style.css # Styles
│ └── js/sync.js # Sync client
├── views/
│ ├── layouts/main.html # Main layout
│ └── pages/home.html # Home page
├── go.mod
└── .gitignore
Core Files
app/server/app.go
package server
import (
" github.com/go-mizu/mizu "
" github.com/go-mizu/mizu/live "
" github.com/go-mizu/mizu/sync "
synchttp " github.com/go-mizu/mizu/sync/http "
" github.com/go-mizu/mizu/sync/memory "
" github.com/go-mizu/mizu/view "
" example.com/myapp/service/todo "
)
type App struct {
cfg Config
app * mizu . App
engine * view . Engine
syncEngine * sync . Engine
syncTransport * synchttp . Transport
liveServer * live . Server
store * todo . Store
}
func New ( cfg Config ) * App {
a := & App { cfg : cfg }
a . app = mizu . New ()
a . store = todo . NewStore ()
a . setupView ()
a . setupSync ()
a . setupLive ()
a . routes ()
return a
}
func ( a * App ) setupView () {
a . engine = view . New ( view . Options {
Root : "views" ,
Layout : "layouts/main" ,
})
a . app . Use ( a . engine . Middleware ())
}
func ( a * App ) setupSync () {
log := memory . NewLog ()
dedupe := memory . NewDedupe ()
a . syncEngine = sync . New ( sync . Options {
Log : log ,
Apply : a . store . Apply ,
Snapshot : a . store . Snapshot ,
Dedupe : dedupe ,
})
a . syncTransport = synchttp . New ( synchttp . Options {
Engine : a . syncEngine ,
})
}
func ( a * App ) setupLive () {
a . liveServer = live . NewServer ( live . Options {})
}
service/todo/mutator.go
package todo
import (
" context "
" encoding/json "
" fmt "
gosync " sync "
" github.com/go-mizu/mizu/sync "
)
type Store struct {
mu gosync . RWMutex
todos map [ string ] map [ string ] * Todo // scope -> id -> todo
}
type Todo struct {
ID string `json:"id"`
Title string `json:"title"`
Completed bool `json:"completed"`
}
func NewStore () * Store {
return & Store {
todos : make ( map [ string ] map [ string ] * Todo ),
}
}
// Apply handles mutations (sync.ApplyFunc)
func ( s * Store ) Apply ( ctx context . Context , m sync . Mutation ) ([] sync . Change , error ) {
s . mu . Lock ()
defer s . mu . Unlock ()
scope := m . Scope
if s . todos [ scope ] == nil {
s . todos [ scope ] = make ( map [ string ] * Todo )
}
switch m . Type {
case "todo.create" :
return s . createTodo ( scope , m . Args )
case "todo.toggle" :
return s . toggleTodo ( scope , m . Args )
case "todo.delete" :
return s . deleteTodo ( scope , m . Args )
default :
return nil , fmt . Errorf ( "unknown mutation: %s " , m . Type )
}
}
func ( s * Store ) createTodo ( scope string , args json . RawMessage ) ([] sync . Change , error ) {
var input struct {
ID string `json:"id"`
Title string `json:"title"`
}
if err := json . Unmarshal ( args , & input ); err != nil {
return nil , err
}
todo := & Todo {
ID : input . ID ,
Title : input . Title ,
Completed : false ,
}
s . todos [ scope ][ todo . ID ] = todo
data , _ := json . Marshal ( s . todos [ scope ])
return [] sync . Change {{ Scope : scope , Data : data }}, nil
}
// Snapshot returns current state (sync.SnapshotFunc)
func ( s * Store ) Snapshot ( ctx context . Context , scope string ) ( json . RawMessage , uint64 , error ) {
s . mu . RLock ()
defer s . mu . RUnlock ()
todos := s . GetAll ( scope )
data , _ := json . Marshal ( todos )
return data , 0 , nil
}
// GetAll returns all todos for a scope
func ( s * Store ) GetAll ( scope string ) [] * Todo {
s . mu . RLock ()
defer s . mu . RUnlock ()
if s . todos [ scope ] == nil {
return [] * Todo {}
}
todos := make ([] * Todo , 0 , len ( s . todos [ scope ]))
for _ , t := range s . todos [ scope ] {
todos = append ( todos , t )
}
return todos
}
app/server/routes.go
package server
import " example.com/myapp/handler "
func ( a * App ) routes () {
a . app . Mount ( "/static/" , staticHandler ( a . cfg . Dev ))
a . app . Get ( "/" , handler . Home ( a . store ))
a . syncTransport . Mount ( a . app ) // Mounts at /_sync/*
a . app . Mount ( "/ws" , a . liveServer . Handler ())
}
assets/js/sync.js
class SyncClient {
constructor ( url ) {
this . url = url ;
this . pending = [];
}
async mutate ( type , args ) {
const mutation = {
id: crypto . randomUUID (),
type ,
args ,
};
// Optimistic: apply locally
this . applyLocal ( mutation );
// Queue for sync
this . pending . push ( mutation );
await this . sync ();
}
async sync () {
if ( this . pending . length === 0 ) return ;
try {
const res = await fetch ( this . url + '/_sync/push' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({ mutations: this . pending }),
});
if ( res . ok ) {
this . pending = [];
}
} catch ( e ) {
// Will retry later
}
}
}
Sync Flow
Client Mutation - User action creates mutation
Local Apply - UI updates optimistically
Push to Server - Mutation sent to /_sync/push
Server Apply - Store.Apply processes mutation
Broadcast - Changes sent to all clients
UI Sync - All clients update
Next Steps
Tutorial Build a syncing todo app
Sync Documentation Deep dive into sync