ooo

State management with real-time network access. Fast, zero-configuration layer for storing and synchronizing application state using JSON Patch updates.

View on GitHub Get Started
ooo UI demo

How It Works

Interactive visualization of real-time state management

Click buttons or press keys: S Set, P Push, W Subscribe, D Delete

Features

Everything you need to build realtime applications

Dynamic Routing

Automatic route handling with glob pattern support for lists and collections.

Real-time Updates

WebSocket subscriptions with automatic reconnection and JSON patch updates.

Filters & Audit

Built-in middleware for validation, transformation, and access control.

Auto Timestamps

Automatic created/updated timestamps using monotonic clock for ordering consistency.

Built-in UI

Web-based admin interface for browsing and managing your data.

Typed Generics

Go generics support for type-safe data operations and subscriptions.

Quickstart

Get up and running in minutes

Install
go get github.com/benitogf/ooo
main.go
package main

import "github.com/benitogf/ooo"

func main() {
    server := ooo.Server{}
    server.Start("0.0.0.0:8800")
    server.WaitClose()
}
What this does:
  • Starts an HTTP/WebSocket server on port 8800 with a built-in web UI
  • No filters set and Static mode is off — you can read/write any path
  • Default storage is in-memory. Use ko for LevelDB or nopog for PostgreSQL persistence
  • Supports real-time subscriptions via WebSocket with JSON Patch updates

API Reference

RESTful HTTP and WebSocket endpoints

UI & Management

Method Description URL
GET Web interface http://{host}:{port}/
GET List all keys (paginated) http://{host}:{port}/?api=keys
GET Server info http://{host}:{port}/?api=info
GET Filter paths http://{host}:{port}/?api=filters
GET Connection state http://{host}:{port}/?api=state

↳ Keys API Query Parameters

Optional parameters for the ?api=keys endpoint above

Parameter Description Default
page Page number (1-indexed) 1
limit Items per page (max 500) 50
filter Filter by prefix or glob pattern (none)

Data Operations

Method Description URL
POST Create/Update http://{host}:{port}/{key}
GET Read http://{host}:{port}/{key}
PATCH Partial update (JSON Patch) http://{host}:{port}/{key}
DELETE Delete http://{host}:{port}/{key}

WebSocket

Method Description URL
WS Server clock ws://{host}:{port}
WS Subscribe to updates ws://{host}:{port}/{key}

Filters

Control access and transform data. Paths support glob patterns (*) and multi-level globs like users/*/posts/*.

Filter Description
OpenFilter Enable route (required in static mode)
WriteFilter Transform/validate before write
AfterWriteFilter Callback after write completes
ReadObjectFilter Transform single object on read
ReadListFilter Transform list items on read
DeleteFilter Control delete operations
LimitFilter Maintain max entries in a list (auto-cleanup)
Filter Examples
// Enable a route (required when Static=true)
server.OpenFilter("books/*")

// Validate/transform before write
server.WriteFilter("books/*", func(index string, data json.RawMessage) (json.RawMessage, error) {
    return data, nil
})

// Limit list to N most recent entries (auto-deletes oldest)
server.LimitFilter("logs/*", filters.LimitFilterConfig{Limit: 100})

// With ascending order (oldest first in results)
server.LimitFilter("events/*", filters.LimitFilterConfig{
    Limit: 50,
    Order: filters.OrderAsc,
})

Custom Endpoints

Register custom HTTP endpoints with typed schemas visible in the storage explorer UI.

Field Description
Path URL path with optional route variables like /policies/{id}
Vars Route variables (mandatory) - auto-extracted from path if not specified
Methods Map of HTTP methods to their specifications
MethodSpec.Params Query parameters (optional) - per method
MethodSpec.Request Go type for request body schema
MethodSpec.Response Go type for response body schema
Custom Endpoint Example
server.Endpoint(ooo.EndpointConfig{
    Path:        "/policies/{id}",
    Description: "Manage access control policies",
    // Vars are route variables (mandatory) - auto-extracted from {id} in path
    Vars: ooo.Vars{"id": "Policy ID"},
    Methods: ooo.Methods{
        "GET": ooo.MethodSpec{
            Response: PolicyResponse{},
            // Params are query parameters (optional) - per method
            Params: ooo.Params{"filter": "Optional filter value"},
        },
        "PUT": ooo.MethodSpec{
            Request:  Policy{},
            Response: PolicyResponse{},
        },
    },
    Handler: func(w http.ResponseWriter, r *http.Request) {
        id := mux.Vars(r)["id"]           // Route variable (mandatory)
        filter := r.URL.Query().Get("filter") // Query param (optional)
        // ... handle request
    },
})

Proxies

Forward filters from remote ooo servers with path remapping.

Function Description
proxy.Route Proxy single object paths (e.g., settings/*)
proxy.RouteList Proxy list paths with nested items (e.g., items/*/*)
Proxy Route Example
import "github.com/benitogf/ooo/proxy"

// Proxy /settings/{deviceID} → /settings on remote
proxy.Route(server, "settings/*", proxy.Config{
    Resolve: func(localPath string) (address, remotePath string, err error) {
        return "localhost:8800", "settings", nil
    },
})

// Proxy list: /items/{deviceID}/* → /items/* on remote
proxy.RouteList(server, "items/*/*", proxy.Config{
    Resolve: func(localPath string) (address, remotePath string, err error) {
        parts := strings.SplitN(localPath, "/", 3)
        if len(parts) == 3 {
            return "localhost:8800", "items/" + parts[2], nil
        }
        return "localhost:8800", "items/*", nil
    },
})

I/O Operations

Type-safe data operations with Go generics. Click to expand code.

Local Operations

Get

Retrieve a single item from the specified path

Get
// Get retrieves a single item from the specified path
item, err := ooo.Get[YourType](server, "path/to/item")
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Item: %+v\n", item.Data)

GetList

Retrieve all items from a list path (glob pattern)

GetList
// GetList retrieves all items from a list path (ends with "/*")
items, err := ooo.GetList[YourType](server, "path/to/items/*")
if err != nil {
    log.Fatal(err)
}
for _, item := range items {
    fmt.Printf("Item: %+v (created: %v)\n", item.Data, item.Created)
}

Set

Create or update an item at the specified path

Set
// Set creates or updates an item at the specified path
err := ooo.Set(server, "path/to/item", YourType{
    Field1: "value1",
    Field2: "value2",
})
if err != nil {
    log.Fatal(err)
}

Push

Add an item to a list (path must end with "/*")

Push
// Push adds an item to a list (path must end with "/*")
index, err := ooo.Push(server, "path/to/items/*", YourType{
    Field1: "new item",
    Field2: "another value",
})
if err != nil {
    log.Fatal(err)
}
fmt.Println("Created at:", index)

Delete

Remove item(s) at the specified path or glob pattern

Delete
// Delete removes item(s) at the specified path
// For single item: ooo.Delete(server, "path/to/item")
// For glob pattern: ooo.Delete(server, "items/*") removes all matching items
err := ooo.Delete(server, "path/to/item")
if err != nil {
    log.Fatal(err)
}

Remote Operations

RemoteConfig
cfg := io.RemoteConfig{
    Client: &http.Client{Timeout: 10 * time.Second},
    Host:   "localhost:8800",
    SSL:    false, // set to true for HTTPS
}

RemoteGet / RemoteGetList

Fetch single item or list from remote server

RemoteGet
item, err := io.RemoteGet[YourType](cfg, "path/to/item")

items, err := io.RemoteGetList[YourType](cfg, "path/to/items/*")

RemoteSet / RemotePush

Create/update item or add to list on remote server

RemoteSet
err := io.RemoteSet(cfg, "path/to/item", YourType{Field1: "value"})

err := io.RemotePush(cfg, "path/to/items/*", YourType{Field1: "new item"})

RemoteDelete

Delete item(s) from remote server (supports glob patterns)

RemoteDelete
// Delete single item or use glob pattern for multiple items
err := io.RemoteDelete(cfg, "path/to/item")
err := io.RemoteDelete(cfg, "items/*")

Subscribe Clients

Real-time WebSocket subscriptions with Go generics

SubscribeList
go client.SubscribeList(client.SubscribeConfig{
    Ctx:  ctx,
    Server: client.Server{Protocol: "ws", Host: "localhost:8800"},
}, "items/*", client.SubscribeListEvents[Item]{
    OnMessage: func(items []client.Meta[Item]) { /* handle */ },
    OnError:   func(err error) { /* handle */ },
})
Subscribe (single item)
go client.Subscribe(client.SubscribeConfig{
    Ctx:  ctx,
    Server: client.Server{Protocol: "ws", Host: "localhost:8800"},
}, "config", client.SubscribeEvents[Config]{
    OnMessage: func(item client.Meta[Config]) { /* handle */ },
    OnError:   func(err error) { /* handle */ },
})

For JavaScript, use ooo-client.

Samples

Complete examples demonstrating various features. Click to expand code.

01 - Basic Server

Minimal server setup - the simplest way to start an ooo server

main.go
package main

import "github.com/benitogf/ooo"

func main() {
	server := ooo.Server{}
	server.Start("0.0.0.0:8800")
	server.WaitClose()
}

02 - Static Routes, Filters & Audit

Static mode, write/read/delete filters, and API key authentication

main.go
package main

import (
	"encoding/json"
	"errors"
	"log"
	"net/http"

	"github.com/benitogf/ooo"
	"github.com/benitogf/ooo/meta"
)

type Book struct {
	Title  string `json:"title"`
	Author string `json:"author"`
	Secret string `json:"secret,omitempty"`
}

func main() {
	server := ooo.Server{Static: true}

	// Only allow requests that carry a valid API key
	server.Audit = func(r *http.Request) bool {
		return r.Header.Get("X-API-Key") == "secret"
	}

	// Make the list route available while Static mode is enabled
	server.OpenFilter("books/*")
	server.OpenFilter("books/locked") // single resource example

	// Sanitize/validate before writes to the list
	server.WriteFilter("books/*", func(index string, data json.RawMessage) (json.RawMessage, error) {
		var b Book
		err := json.Unmarshal(data, &b)
		if err != nil {
			return nil, err
		}
		if b.Title == "" {
			return nil, errors.New("title is required")
		}
		if b.Author == "" {
			b.Author = "unknown"
		}
		// Persist possibly modified payload
		out, _ := json.Marshal(b)
		return out, nil
	})

	// Log after any write
	server.AfterWriteFilter("books/*", func(index string) {
		log.Println("wrote book at", index)
	})

	// Hide secrets on reads (for lists)
	server.ReadListFilter("books/*", func(index string, objs []meta.Object) ([]meta.Object, error) {
		for i := range objs {
			var b Book
			json.Unmarshal(objs[i].Data, &b)
			b.Secret = ""
			objs[i].Data, _ = json.Marshal(b)
		}
		return objs, nil
	})

	// Prevent deleting a specific resource
	server.DeleteFilter("books/locked", func(key string) error {
		return errors.New("this book cannot be deleted")
	})

	server.Start("0.0.0.0:8800")
	server.WaitClose()
}

03 - Storage API

Direct storage read/write operations without HTTP

main.go
package main

import (
	"encoding/json"
	"log"
	"strconv"

	"github.com/benitogf/ooo"
	"github.com/benitogf/ooo/monotonic"
)

type Game struct {
	Started int64 `json:"started"`
}

func main() {
	// Initialize the monotonic clock for consistent timestamps
	monotonic.Init()
	defer monotonic.Stop()

	// Create a static server - only filtered routes are accessible
	server := ooo.Server{Static: true}

	// Define the path so it's available through HTTP/WebSocket
	server.OpenFilter("game")

	// Start the server with default memory storage
	server.Start("0.0.0.0:8800")

	// Write data using monotonic timestamp for ordering consistency
	// monotonic.Now() returns nanoseconds that always increase
	timestamp := strconv.FormatInt(monotonic.Now(), 10)
	index, err := server.Storage.Set("game", json.RawMessage(`{"started": `+timestamp+`}`))
	if err != nil {
		log.Fatal(err)
	}
	log.Println("stored in", index)

	// Read data back from storage
	dataObject, err := server.Storage.Get("game")
	if err != nil {
		log.Fatal(err)
	}
	log.Println("created:", dataObject.Created)
	log.Println("updated:", dataObject.Updated)
	log.Println("data:", string(dataObject.Data))

	// Parse JSON to struct
	game := Game{}
	err = json.Unmarshal(dataObject.Data, &game)
	if err != nil {
		log.Fatal(err)
	}
	log.Println("started:", game.Started)

	server.WaitClose()
}

04 - I/O Operations

Typed I/O helpers with Go generics (Get, Set, Push, GetList)

main.go
package main

import (
	"fmt"
	"log"
	"time"

	"github.com/benitogf/ooo"
)

type Todo struct {
	Task      string    `json:"task"`
	Completed bool      `json:"completed"`
	Due       time.Time `json:"due"`
}

func main() {
	// Start a local server for testing
	server := &ooo.Server{Silence: true}
	server.Start("localhost:0")
	defer server.Close(nil)

	// Add some todos using Push (for lists)
	_, err := ooo.Push(server, "todos/*", Todo{
		Task:      "todo 1",
		Completed: false,
		Due:       time.Now().Add(24 * time.Hour),
	})
	if err != nil {
		log.Fatal(err)
	}

	_, err = ooo.Push(server, "todos/*", Todo{
		Task:      "todo 2",
		Completed: false,
		Due:       time.Now().Add(48 * time.Hour),
	})
	if err != nil {
		log.Fatal(err)
	}

	// Get all todos using GetList
	todos, err := ooo.GetList[Todo](server, "todos/*")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("All todos:")
	for i, todo := range todos {
		fmt.Printf("%d. %s (Due: %v)\n", i+1, todo.Data.Task, todo.Data.Due)
	}

	// Set a single item
	err = ooo.Set(server, "config", map[string]string{"theme": "dark"})
	if err != nil {
		log.Fatal(err)
	}

	// Get a single item
	config, err := ooo.Get[map[string]string](server, "config")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Config: %+v\n", config.Data)
}

05 - WebSocket Subscribe List

Real-time subscription to list paths with typed callbacks

main.go
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/benitogf/ooo"
	"github.com/benitogf/ooo/client"
)

type Todo struct {
	Task      string    `json:"task"`
	Completed bool      `json:"completed"`
	Due       time.Time `json:"due"`
}

func main() {
	// Start server (for demo)
	server := &ooo.Server{Silence: true}
	server.Start("localhost:0")
	defer server.Close(nil)

	// Seed one item
	ooo.Push(server, "todos/*", Todo{Task: "seed", Due: time.Now().Add(1 * time.Hour)})

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	cfg := client.SubscribeConfig{
		Ctx:     ctx,
		Server:  client.Server{Protocol: "ws", Host: server.Address},
		Silence: true,
	}

	go client.SubscribeList[Todo](cfg, "todos/*", client.SubscribeListEvents[Todo]{
		OnMessage: func(items []client.Meta[Todo]) {
			fmt.Println("list size:", len(items))
			for i, it := range items {
				fmt.Printf("%d. %s (due: %v)\n", i+1, it.Data.Task, it.Data.Due)
			}
		},
		OnError: func(err error) {
			fmt.Println("connection error:", err)
		},
	})

	// Produce updates
	time.Sleep(50 * time.Millisecond)
	ooo.Push(server, "todos/*", Todo{Task: "another", Due: time.Now().Add(2 * time.Hour)})

	time.Sleep(300 * time.Millisecond)
}

06 - WebSocket Subscribe Single

Real-time subscription to single item paths

main.go
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/benitogf/ooo"
	"github.com/benitogf/ooo/client"
)

type Todo struct {
	Task      string    `json:"task"`
	Completed bool      `json:"completed"`
	Due       time.Time `json:"due"`
}

func main() {
	// Start server (for demo)
	server := &ooo.Server{Silence: true}
	server.Start("localhost:0")
	defer server.Close(nil)

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	cfg := client.SubscribeConfig{
		Ctx:    ctx,
		Server: client.Server{Protocol: "ws", Host: server.Address},
	}

	// Ensure path exists
	ooo.Set(server, "todo", Todo{Task: "one", Due: time.Now().Add(24 * time.Hour)})

	go client.Subscribe[Todo](cfg, "todo", client.SubscribeEvents[Todo]{
		OnMessage: func(item client.Meta[Todo]) {
			fmt.Println("current todo:", item.Data.Task)
		},
		OnError: func(err error) {
			fmt.Println("connection error:", err)
		},
	})

	// Update the item to trigger a message
	time.Sleep(50 * time.Millisecond)
	ooo.Set(server, "todo", Todo{Task: "updated", Due: time.Now().Add(48 * time.Hour)})

	time.Sleep(300 * time.Millisecond)
}

07 - WebSocket Subscribe Multiple

Subscribe to multiple list paths with different types using WaitGroup synchronization

main.go
package main

import (
	"context"
	"fmt"
	"sync"

	"github.com/benitogf/ooo"
	"github.com/benitogf/ooo/client"
)

type Product struct {
	ID    int    `json:"id"`
	Name  string `json:"name"`
	Price int    `json:"price"`
}

type Order struct {
	ID        int `json:"id"`
	ProductID int `json:"product_id"`
	Quantity  int `json:"quantity"`
}

func main() {
	// Start server for demo
	server := &ooo.Server{Silence: true}
	server.Start("localhost:0")
	defer server.Close(nil)

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	cfg := client.SubscribeConfig{
		Ctx:     ctx,
		Server:  client.Server{Protocol: "ws", Host: server.Address},
		Silence: true,
	}

	// Use WaitGroup to synchronize async operations
	var wg sync.WaitGroup
	wg.Add(3) // Expect 3 updates

	go client.SubscribeMultipleList2(
		cfg,
		[2]string{"products/*", "orders/*"},
		client.SubscribeMultipleList2Events[Product, Order]{
			OnMessage: func(products client.MultiState[Product], orders client.MultiState[Order]) {
				// Called when either subscription updates
				// Use .Updated to check which one changed
				if products.Updated {
					fmt.Println("products updated:", len(products.Data))
					wg.Done()
				}
				if orders.Updated {
					fmt.Println("orders updated:", len(orders.Data))
					wg.Done()
				}
			},
			OnError: func(productsErr, ordersErr error) {
				if productsErr != nil {
					fmt.Println("products error:", productsErr)
				}
				if ordersErr != nil {
					fmt.Println("orders error:", ordersErr)
				}
			},
		},
	)

	// Create some data - updates trigger OnMessage callbacks
	ooo.Push(server, "products/*", Product{ID: 1, Name: "Widget", Price: 100})
	ooo.Push(server, "orders/*", Order{ID: 1, ProductID: 1, Quantity: 5})
	ooo.Push(server, "products/*", Product{ID: 2, Name: "Gadget", Price: 200})

	wg.Wait()
}

08 - Remote I/O Operations

HTTP client operations with RemoteGet, RemoteSet, RemotePush, RemoteDelete

main.go
package main

import (
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/benitogf/ooo"
	"github.com/benitogf/ooo/io"
)

type Todo struct {
	Task      string    `json:"task"`
	Completed bool      `json:"completed"`
	Due       time.Time `json:"due"`
}

func main() {
	// Start a local server for testing
	server := &ooo.Server{Silence: true}
	server.Start("localhost:0")
	defer server.Close(nil)

	// Create a remote config
	cfg := io.RemoteConfig{
		Client: &http.Client{Timeout: 10 * time.Second},
		Host:   server.Address,
		SSL:    false, // set to true for HTTPS
	}

	// RemoteSet - create/update a single item
	err := io.RemoteSet(cfg, "config", map[string]string{"theme": "dark"})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Set config successfully")

	// RemoteGet - retrieve a single item
	config, err := io.RemoteGet[map[string]string](cfg, "config")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Got config: %+v\n", config.Data)

	// RemotePush - add items to a list
	err = io.RemotePush(cfg, "todos/*", Todo{
		Task:      "First task",
		Completed: false,
		Due:       time.Now().Add(24 * time.Hour),
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Pushed first todo")

	err = io.RemotePush(cfg, "todos/*", Todo{
		Task:      "Second task",
		Completed: false,
		Due:       time.Now().Add(48 * time.Hour),
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Pushed second todo")

	// RemoteGetList - retrieve all items from a list
	todos, err := io.RemoteGetList[Todo](cfg, "todos/*")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Got %d todos:\n", len(todos))
	for i, todo := range todos {
		fmt.Printf("  %d. %s (due: %v)\n", i+1, todo.Data.Task, todo.Data.Due)
	}

	// RemoteDelete - delete an item
	err = io.RemoteDelete(cfg, "config")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Deleted config")
}

09 - Custom Endpoints

Demonstrates `server.Endpoint()` for registering custom HTTP endpoints with typed schemas visible in the storage explorer UI.

main.go
package main

import (
	"encoding/json"
	"log"
	"net/http"
	"strings"
	"sync"

	"github.com/benitogf/ooo"
	"github.com/gorilla/mux"
)

// Policy represents an access control policy
type Policy struct {
	Name        string   `json:"name"`
	Description string   `json:"description"`
	Permissions []string `json:"permissions"`
}

// PolicyResponse is returned when getting a policy
type PolicyResponse struct {
	ID          string   `json:"id"`
	Name        string   `json:"name"`
	Description string   `json:"description"`
	Permissions []string `json:"permissions"`
}

// policies stores our in-memory data
var (
	policies   = make(map[string]Policy)
	policiesMu sync.RWMutex
)

func main() {
	server := ooo.Server{
		Static: true,
		Name:   "Custom Endpoints Demo",
	}

	// Register a custom endpoint for policies
	// The endpoint metadata is visible in the storage explorer UI
	server.Endpoint(ooo.EndpointConfig{
		Path:        "/policies/{id}",
		Description: "Manage access control policies",
		Methods: ooo.Methods{
			"GET": ooo.MethodSpec{
				Response: PolicyResponse{},
			},
			"PUT": ooo.MethodSpec{
				Request:  Policy{},
				Response: PolicyResponse{},
			},
			"DELETE": ooo.MethodSpec{},
		},
		Handler: func(w http.ResponseWriter, r *http.Request) {
			id := mux.Vars(r)["id"]

			switch r.Method {
			case "GET":
				policiesMu.RLock()
				p, ok := policies[id]
				policiesMu.RUnlock()
				if !ok {
					http.Error(w, "policy not found", http.StatusNotFound)
					return
				}
				json.NewEncoder(w).Encode(PolicyResponse{
					ID:          id,
					Name:        p.Name,
					Description: p.Description,
					Permissions: p.Permissions,
				})

			case "PUT":
				var p Policy
				if err := json.NewDecoder(r.Body).Decode(&p); err != nil {
					http.Error(w, err.Error(), http.StatusBadRequest)
					return
				}
				policiesMu.Lock()
				policies[id] = p
				policiesMu.Unlock()
				w.WriteHeader(http.StatusCreated)
				json.NewEncoder(w).Encode(PolicyResponse{
					ID:          id,
					Name:        p.Name,
					Description: p.Description,
					Permissions: p.Permissions,
				})

			case "DELETE":
				policiesMu.Lock()
				delete(policies, id)
				policiesMu.Unlock()
				w.WriteHeader(http.StatusNoContent)
			}
		},
	})

	// Register a list endpoint with optional query parameter filtering
	// Params are per-method query parameters shown in the UI
	server.Endpoint(ooo.EndpointConfig{
		Path:        "/policies",
		Description: "List and search policies",
		Methods: ooo.Methods{
			"GET": ooo.MethodSpec{
				Response: []PolicyResponse{},
				Params: ooo.Params{
					"name": "Filter policies by name (partial match)",
				},
			},
		},
		Handler: func(w http.ResponseWriter, r *http.Request) {
			nameFilter := r.URL.Query().Get("name")

			policiesMu.RLock()
			result := make([]PolicyResponse, 0, len(policies))
			for id, p := range policies {
				// Apply optional name filter
				if nameFilter != "" && !strings.Contains(strings.ToLower(p.Name), strings.ToLower(nameFilter)) {
					continue
				}
				result = append(result, PolicyResponse{
					ID:          id,
					Name:        p.Name,
					Description: p.Description,
					Permissions: p.Permissions,
				})
			}
			policiesMu.RUnlock()
			json.NewEncoder(w).Encode(result)
		},
	})

	server.Start("0.0.0.0:8800")
	log.Println("Server running with custom endpoints")
	log.Println("")
	log.Println("Try these commands:")
	log.Println("  curl -X PUT http://localhost:8800/policies/admin -d '{\"name\":\"Admin\",\"description\":\"Full access\",\"permissions\":[\"read\",\"write\",\"delete\"]}'")
	log.Println("  curl http://localhost:8800/policies/admin")
	log.Println("  curl http://localhost:8800/policies")
	log.Println("  curl -X DELETE http://localhost:8800/policies/admin")
	log.Println("")
	log.Println("Visit http://localhost:8800 to see endpoints in the storage explorer")
	server.WaitClose()
}

10 - Persistent Storage with Ko

LevelDB-backed persistent storage using the ko package

main.go
// To use: go get github.com/benitogf/ko

//
// Requires: go get github.com/benitogf/ko
package main

import (
	"log"

	"github.com/benitogf/ko"
	"github.com/benitogf/ooo"
	"github.com/benitogf/ooo/storage"
)

func main() {
	// Create persistent storage with LevelDB backend
	store := &ko.Storage{Path: "./data"}
	err := store.Start([]string{}, nil)
	if err != nil {
		log.Fatal(err)
	}

	// Consume storage broadcast channel (required)
	go storage.WatchStorageNoop(store)

	// Create server with persistent storage
	server := ooo.Server{
		Storage: store,
	}

	server.Start("0.0.0.0:8800")
	log.Println("Server running with persistent storage at ./data")
	server.WaitClose()
}

11 - JWT Authentication

JWT-based authentication using the auth package

main.go
// To use: go get github.com/benitogf/auth github.com/benitogf/ko

//
// Requires: go get github.com/benitogf/auth
package main

import (
	"log"
	"net/http"
	"time"

	"github.com/benitogf/auth"
	"github.com/benitogf/ko"
	"github.com/benitogf/ooo"
	"github.com/benitogf/ooo/storage"
	"github.com/gorilla/mux"
)

func main() {
	// Auth storage for users
	authStore := &ko.Storage{Path: "./auth_data"}
	err := authStore.Start([]string{}, nil)
	if err != nil {
		log.Fatal(err)
	}
	go storage.WatchStorageNoop(authStore)

	// Create JWT auth with 10 minute token expiry
	tokenAuth := auth.New(
		auth.NewJwtStore("your-secret-key", time.Minute*10),
		authStore,
	)

	// Create server with static mode
	server := ooo.Server{Static: true}

	// Audit middleware - check JWT for protected routes
	server.Audit = func(r *http.Request) bool {
		// Public routes
		if r.URL.Path == "/register" || r.URL.Path == "/authorize" {
			return true
		}
		// Protected routes require valid token
		return tokenAuth.Verify(r)
	}

	// Enable data routes
	server.OpenFilter("data/*")

	// Add auth routes (/register, /authorize, /verify)
	server.Router = mux.NewRouter()
	tokenAuth.Router(&server)

	server.Start("0.0.0.0:8800")
	log.Println("Server running with JWT auth")
	log.Println("POST /register - register user")
	log.Println("POST /authorize - login and get token")
	log.Println("GET /data/* - protected route (requires token)")
	server.WaitClose()
}

12 - Limit Filter

Capped collections with automatic cleanup of oldest entries

main.go
package main

import (
	"log"

	"github.com/benitogf/ooo"
)

func main() {
	server := ooo.Server{Static: true}
	server.Start("0.0.0.0:8800")

	// Keep only the 100 most recent log entries
	// When a new entry is added and count > 100, oldest is deleted
	server.LimitFilter("logs/*", ooo.LimitFilterConfig{Limit: 100})

	// Keep only 50 most recent notifications per user
	server.LimitFilter("notifications/*", ooo.LimitFilterConfig{Limit: 50})

	log.Println("Server running with limit filters")
	log.Println("POST /logs/* - capped at 100 entries")
	log.Println("POST /notifications/* - capped at 50 entries")
	server.WaitClose()
}

13 - Limit Filter with Validation

Capped collections with strict schema validation on writes

main.go
package main

import (
	"encoding/json"
	"errors"
	"log"
	"strings"

	"github.com/benitogf/ooo"
)

// LogEntry defines the exact schema for log entries
type LogEntry struct {
	Level   string `json:"level"`
	Message string `json:"message"`
}

func main() {
	server := ooo.Server{Static: true}
	server.Start("0.0.0.0:8800")

	// Add write validation that enforces strict schema
	server.WriteFilter("logs/*", func(index string, data json.RawMessage) (json.RawMessage, error) {
		// Use DisallowUnknownFields to reject extra fields
		decoder := json.NewDecoder(strings.NewReader(string(data)))
		decoder.DisallowUnknownFields()

		var entry LogEntry
		if err := decoder.Decode(&entry); err != nil {
			return nil, errors.New("invalid schema: " + err.Error())
		}

		// Validate required fields
		if entry.Level == "" {
			return nil, errors.New("level is required")
		}
		if entry.Message == "" {
			return nil, errors.New("message is required")
		}

		// Validate level values
		validLevels := map[string]bool{"debug": true, "info": true, "warn": true, "error": true}
		if !validLevels[entry.Level] {
			return nil, errors.New("level must be one of: debug, info, warn, error")
		}

		return data, nil
	})

	// Add limit filter to cap entries
	server.LimitFilter("logs/*", ooo.LimitFilterConfig{Limit: 100})

	log.Println("Server running with validated limit filter")
	log.Println("POST /logs/* - validates schema and caps at 100 entries")
	log.Println("")
	log.Println("Valid request:")
	log.Println(`  curl -X POST http://localhost:8800/logs/1 -d '{"level":"info","message":"test"}'`)
	log.Println("")
	log.Println("Invalid requests (will be rejected):")
	log.Println(`  curl -X POST http://localhost:8800/logs/1 -d '{"level":"info"}'  # missing message`)
	log.Println(`  curl -X POST http://localhost:8800/logs/1 -d '{"level":"info","message":"test","extra":"field"}'  # extra field`)
	server.WaitClose()
}

14 - Custom Endpoints with nopog

PostgreSQL storage with custom HTTP endpoints, JSONB field queries, and range queries by timestamp.

main.go
// To use: go get github.com/benitogf/nopog

// Custom HTTP endpoints with PostgreSQL storage using nopog.
//
// Requires: go get github.com/benitogf/nopog
// Requires: PostgreSQL running on localhost
package main

import (
	"encoding/json"
	"log"
	"net/http"
	"strconv"

	"github.com/benitogf/nopog"
	"github.com/benitogf/ooo"
	"github.com/benitogf/ooo/key"
	"github.com/gorilla/mux"
)

// Product represents an item in the catalog
type Product struct {
	Name     string `json:"name"`
	Category string `json:"category"`
	Price    int    `json:"price"`
}

// ProductResponse includes the key in the response
type ProductResponse struct {
	Key      string `json:"key"`
	Name     string `json:"name"`
	Category string `json:"category"`
	Price    int    `json:"price"`
}

// CreateResponse is returned after creating a product
type CreateResponse struct {
	Created int64  `json:"created"`
	Key     string `json:"key"`
}

func main() {
	// PostgreSQL storage for large-scale persistent data
	db := &nopog.Storage{
		Name:     "mydb",
		Host:     "localhost",
		User:     "postgres",
		Password: "postgres",
	}
	if err := db.Start(); err != nil {
		log.Fatal("Failed to connect:", err)
	}
	defer db.Close()

	// Create table (idempotent)
	if err := db.CreateTable("catalog"); err != nil {
		log.Fatal(err)
	}

	server := ooo.Server{
		Static: true,
		Name:   "Nopog PostgreSQL Demo",
	}

	// List and create products endpoint
	server.Endpoint(ooo.EndpointConfig{
		Path:        "/api/products",
		Description: "List all products or create a new product",
		Methods: ooo.Methods{
			"GET": ooo.MethodSpec{
				Response: []ProductResponse{},
			},
			"POST": ooo.MethodSpec{
				Request:  Product{},
				Response: CreateResponse{},
			},
		},
		Handler: func(w http.ResponseWriter, r *http.Request) {
			switch r.Method {
			case "GET":
				results, err := db.Get("catalog", "products/*")
				if err != nil {
					http.Error(w, err.Error(), 500)
					return
				}
				json.NewEncoder(w).Encode(results)

			case "POST":
				var p Product
				if err := json.NewDecoder(r.Body).Decode(&p); err != nil {
					http.Error(w, err.Error(), 400)
					return
				}
				data, _ := json.Marshal(p)
				productKey := key.Build("products/*")
				ts, err := db.Set("catalog", productKey, string(data))
				if err != nil {
					http.Error(w, err.Error(), 500)
					return
				}
				w.WriteHeader(http.StatusCreated)
				json.NewEncoder(w).Encode(CreateResponse{Created: ts, Key: productKey})
			}
		},
	})

	// Search products by category
	server.Endpoint(ooo.EndpointConfig{
		Path:        "/api/products/search",
		Description: "Search products by category using JSONB field query",
		Params:      ooo.Params{"category": "Product category to filter by"},
		Methods: ooo.Methods{
			"GET": ooo.MethodSpec{
				Response: []ProductResponse{},
			},
		},
		Handler: func(w http.ResponseWriter, r *http.Request) {
			category := r.URL.Query().Get("category")
			results, err := db.GetByField("catalog", "products/*", "category", category)
			if err != nil {
				http.Error(w, err.Error(), 500)
				return
			}
			json.NewEncoder(w).Encode(results)
		},
	})

	// Get recent products by timestamp range
	server.Endpoint(ooo.EndpointConfig{
		Path:        "/api/products/recent",
		Description: "Get recent products by timestamp range (microseconds)",
		Params: ooo.Params{
			"from":  "Start timestamp in microseconds",
			"to":    "End timestamp in microseconds",
			"limit": "Maximum number of results (default: 10)",
		},
		Methods: ooo.Methods{
			"GET": ooo.MethodSpec{
				Response: []ProductResponse{},
			},
		},
		Handler: func(w http.ResponseWriter, r *http.Request) {
			from, _ := strconv.ParseInt(r.URL.Query().Get("from"), 10, 64)
			to, _ := strconv.ParseInt(r.URL.Query().Get("to"), 10, 64)
			limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
			if limit == 0 {
				limit = 10
			}
			results, err := db.GetNRange("catalog", "products/*", from, to, limit)
			if err != nil {
				http.Error(w, err.Error(), 500)
				return
			}
			json.NewEncoder(w).Encode(results)
		},
	})

	// Delete a product by ID
	server.Endpoint(ooo.EndpointConfig{
		Path:        "/api/products/{id}",
		Description: "Delete a product by its UUID",
		Methods: ooo.Methods{
			"DELETE": ooo.MethodSpec{},
		},
		Handler: func(w http.ResponseWriter, r *http.Request) {
			id := mux.Vars(r)["id"]
			if err := db.Del("catalog", "products/"+id); err != nil {
				http.Error(w, err.Error(), 500)
				return
			}
			w.WriteHeader(http.StatusNoContent)
		},
	})

	server.Start("0.0.0.0:8800")
	log.Println("Server running on :8800 with PostgreSQL backend")
	log.Println("")
	log.Println("Visit http://localhost:8800 to see endpoints in the storage explorer")
	server.WaitClose()
}

15 - Pivot Synchronization

Multi-instance synchronization using the pivot package for distributed ooo servers

node_server.go
node_server.go
// To use: go get github.com/benitogf/pivot

// Node servers replicate data from the leader and can accept local writes
// that sync back when connectivity is restored (AP distributed system).
//
// Requires: go get github.com/benitogf/pivot
package main

import (
	"log"

	"github.com/benitogf/ooo"
	"github.com/benitogf/pivot"
)

func main() {
	server := &ooo.Server{}

	// Configure pivot synchronization
	config := pivot.Config{
		Keys: []pivot.Key{
			{Path: "settings"}, // Single item sync
			{Path: "items/*"},  // List sync
		},
		NodesKey:   "nodes/*",        // Node discovery path
		ClusterURL: "localhost:8800", // Address of the cluster leader
	}

	// Setup pivot - modifies server routes and storage hooks
	server = pivot.Setup(server, config)
	server.Start("0.0.0.0:8801")
	log.Println("Node server running on :8801, syncing with cluster leader at localhost:8800")
	server.WaitClose()
}
pivot_server.go
pivot_server.go
// To use: go get github.com/benitogf/pivot

// The leader server is the source of truth that nodes sync with.
// Run this first, then start node servers pointing to this address.
//
// Requires: go get github.com/benitogf/pivot
package main

import (
	"log"

	"github.com/benitogf/ooo"
	"github.com/benitogf/pivot"
)

func main() {
	server := &ooo.Server{}

	// Configure pivot synchronization
	config := pivot.Config{
		Keys: []pivot.Key{
			{Path: "settings"}, // Single item sync
			{Path: "items/*"},  // List sync
		},
		NodesKey:   "nodes/*", // Node discovery path
		ClusterURL: "",        // Empty = this server IS the cluster leader
	}

	// Setup pivot - modifies server routes and storage hooks
	pivot.Setup(server, config)

	server.Start("0.0.0.0:8800")
	log.Println("Cluster leader running on :8800")
	server.WaitClose()
}

16 - 15 - Proxy Routes

Demonstrates using the proxy package to expose routes from remote OOO servers with path remapping.

proxy_server.go
proxy_server.go
// Proxy server that forwards requests to the remote server.
// Demonstrates path remapping - expose different paths on proxy than on source.
//
// Example: Client requests /settings/deviceA on proxy → forwarded to /settings on remote
package main

import (
	"log"
	"strings"

	"github.com/benitogf/ooo"
	"github.com/benitogf/ooo/proxy"
)

func main() {
	server := &ooo.Server{
		Static: true, // Only allow explicitly defined routes
	}

	// Settings route: /settings/{deviceID} → /settings
	// Each resolver is specific to its route for clarity
	settingsConfig := proxy.Config{
		Resolve: func(localPath string) (address, remotePath string, err error) {
			// localPath = "settings/deviceA" → remotePath = "settings"
			return "localhost:8800", "settings", nil
		},
	}

	// Items route: /items/{deviceID}/* → /items/*
	itemsConfig := proxy.Config{
		Resolve: func(localPath string) (address, remotePath string, err error) {
			// localPath = "items/deviceA/abc123" → remotePath = "items/abc123"
			parts := strings.SplitN(localPath, "/", 3)
			if len(parts) == 3 {
				return "localhost:8800", "items/" + parts[2], nil
			}
			return "localhost:8800", "items/*", nil
		},
	}

	// Route: /settings/{deviceID} → /settings on remote
	err := proxy.Route(server, "settings/*", settingsConfig)
	if err != nil {
		log.Fatal("Failed to setup settings route:", err)
	}

	// Route: /items/{deviceID}/* → /items/* on remote
	err = proxy.RouteList(server, "items/*/*", itemsConfig)
	if err != nil {
		log.Fatal("Failed to setup items route:", err)
	}

	server.Start("0.0.0.0:8801")
	log.Println("Proxy server running on :8801")
	log.Println("Forwarding requests to remote at localhost:8800")
	log.Println("")
	log.Println("Path remapping examples:")
	log.Println("  /settings/deviceA  → /settings (remote)")
	log.Println("  /items/deviceA/*   → /items/*  (remote)")
	log.Println("")
	log.Println("Try these commands:")
	log.Println("  curl http://localhost:8801/settings/mydevice")
	log.Println("  curl http://localhost:8801/items/mydevice/*")
	log.Println("  curl -X POST http://localhost:8801/items/mydevice/* -d '{\"name\":\"New Item\"}'")
	server.WaitClose()
}
remote_server.go
remote_server.go
// Remote server that holds the actual data.
package main

import (
	"log"

	"github.com/benitogf/ooo"
)

func main() {
	server := &ooo.Server{}

	// Seed some initial data
	server.Start("0.0.0.0:8800")

	// Add initial settings
	ooo.Set(server, "settings", map[string]any{
		"theme":    "dark",
		"language": "en",
	})

	// Add some items
	ooo.Push(server, "items/*", map[string]any{"name": "Item 1", "value": 100})
	ooo.Push(server, "items/*", map[string]any{"name": "Item 2", "value": 200})

	log.Println("Remote server running on :8800")
	log.Println("This server holds the actual data")
	server.WaitClose()
}

See all samples including ecosystem integrations (ko, auth, nopog, pivot) on GitHub.

Ecosystem

Related projects and integrations