Quick Start
Zero to working sync in 15 minutes. This guide walks you through adding Synchro to an existing Go + PostgreSQL application and connecting a client SDK.
Prerequisites
Section titled “Prerequisites”| Requirement | Minimum Version |
|---|---|
| Go | 1.22+ |
| PostgreSQL | 14+ with wal_level=logical |
1. Install
Section titled “1. Install”go get github.com/trainstar/synchro2. Prepare Your Schema
Section titled “2. Prepare Your Schema”Synchro works with your existing tables. The only requirement is a nullable deleted_at column for soft-delete tracking.
-- Your existing tableCREATE TABLE tasks ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id), title TEXT NOT NULL, priority INTEGER, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), deleted_at TIMESTAMPTZ -- (1)!);- This is the only column Synchro requires. If your table already has a soft-delete column, point
DeletedAtColumnat it.
3. Register Tables
Section titled “3. Register Tables”Tell Synchro which tables to sync and how ownership works.
registry := synchro.NewRegistry()
registry.Register(&synchro.TableConfig{ TableName: "tasks", OwnerColumn: "user_id",})That is the minimal registration. PushPolicy, BucketByColumn, and BucketPrefix are inferred from the OwnerColumn automatically. See Configuration for the full set of options.
4. Create the Engine
Section titled “4. Create the Engine”engine, err := synchro.NewEngine(synchro.Config{ DB: db, // *sql.DB Registry: registry,})The engine validates the registry graph at startup and returns an error if any table references are broken.
5. Run Migrations
Section titled “5. Run Migrations”Synchro uses a small set of sidecar tables (sync_changelog, sync_clients, etc.) alongside your application tables. Run the infrastructure DDL through your migration system, then apply RLS policies.
import "github.com/trainstar/synchro/migrate"
// Infrastructure tablesfor _, stmt := range migrate.Migrations() { if _, err := db.Exec(stmt); err != nil { log.Fatalf("migration failed: %v", err) }}
// Row-level security policiesfor _, stmt := range synchro.GenerateRLSPolicies(registry) { if _, err := db.Exec(stmt); err != nil { log.Fatalf("RLS policy failed: %v", err) }}6. Wire HTTP Handlers
Section titled “6. Wire HTTP Handlers”Synchro provides stdlib net/http handlers. Mount them on any router.
import "github.com/trainstar/synchro/handler"
h := handler.New(engine)
mux := http.NewServeMux()mux.HandleFunc("POST /sync/register", h.ServeRegister)mux.HandleFunc("POST /sync/pull", h.ServePull)mux.HandleFunc("POST /sync/push", h.ServePush)mux.HandleFunc("POST /sync/snapshot", h.ServeSnapshot)mux.HandleFunc("GET /sync/tables", h.ServeTableMeta)mux.HandleFunc("GET /sync/schema", h.ServeSchema)Every POST endpoint requires a user identity in the request context. Use the built-in middleware or inject your own:
// Option A: Header-based (development / API gateway)wrapped := handler.UserIDMiddleware("X-User-ID", mux)
// Option B: JWT-based (production)wrapped := handler.JWTAuthMiddleware(handler.JWTAuthConfig{ JWKSURL: "https://auth.example.com/.well-known/jwks.json", UserClaim: "sub",}, mux)
http.ListenAndServe(":8080", wrapped)7. Start the WAL Consumer
Section titled “7. Start the WAL Consumer”The WAL consumer captures changes from PostgreSQL logical replication and writes them to the changelog.
import "github.com/trainstar/synchro/wal"
consumer := wal.NewConsumer(wal.ConsumerConfig{ ConnString: "postgres://user:pass@localhost:5432/mydb?replication=database", SlotName: "synchro_slot", PublicationName: "synchro_pub", Registry: registry, Assigner: synchro.NewJoinResolverWithDB(registry, db), ChangelogDB: db,})
go consumer.Start(ctx) // blocks until ctx is cancelled8. PostgreSQL Setup
Section titled “8. PostgreSQL Setup”Enable logical replication and create a publication for your synced tables.
-- Enable logical replication (requires restart)ALTER SYSTEM SET wal_level = 'logical';-- Then restart PostgreSQL
-- Create the publicationCREATE PUBLICATION synchro_pub FOR TABLE tasks;To add more tables later:
ALTER PUBLICATION synchro_pub ADD TABLE comments, categories;9. Connect a Client
Section titled “9. Connect a Client”let client = try SynchroClient(config: SynchroConfig( dbPath: "synchro.db", serverURL: URL(string: "https://api.example.com")!, authProvider: { await getToken() }, clientID: "device-123", appVersion: "1.0.0"))try await client.start()
let tasks = try client.query( "SELECT * FROM tasks ORDER BY created_at DESC")Kotlin
Section titled “Kotlin”val client = SynchroClient(SynchroConfig( dbPath = "synchro.db", serverURL = "https://api.example.com", authProvider = { getToken() }, clientID = "device-123", appVersion = "1.0.0"), context)client.start()
val tasks = client.query( "SELECT * FROM tasks ORDER BY created_at DESC")React Native
Section titled “React Native”import { SynchroClient } from '@trainstar/synchro-react-native';
const client = new SynchroClient({ dbPath: 'synchro.db', serverURL: 'https://api.example.com', authProvider: async () => await getToken(), clientID: 'device-123', appVersion: '1.0.0',});await client.start();
const tasks = await client.query( 'SELECT * FROM tasks ORDER BY created_at DESC');The client SDK handles registration, schema sync, snapshot bootstrap, and the ongoing push/pull loop automatically. Write to your local SQLite tables with normal SQL — CDC triggers capture changes and queue them for push.
Next Steps
Section titled “Next Steps”- Core Concepts — Understand how WAL capture, buckets, checkpoints, and conflict resolution work together.
- Configuration — Full reference for
TableConfig,Config, hooks, middleware, and advanced options. - API Reference — Wire protocol specification for building custom clients.