State management with real-time network access. Fast, zero-configuration layer for storing and synchronizing application state using JSON Patch updates.
Interactive visualization of real-time state management
Click buttons or press keys: S Set, P Push, W Subscribe, D Delete
Everything you need to build realtime applications
Automatic route handling with glob pattern support for lists and collections.
WebSocket subscriptions with automatic reconnection and JSON patch updates.
Built-in middleware for validation, transformation, and access control.
Automatic created/updated timestamps using monotonic clock for ordering consistency.
Web-based admin interface for browsing and managing your data.
Go generics support for type-safe data operations and subscriptions.
Get up and running in minutes
go get github.com/benitogf/ooo
package main
import "github.com/benitogf/ooo"
func main() {
server := ooo.Server{}
server.Start("0.0.0.0:8800")
server.WaitClose()
}
RESTful HTTP and WebSocket endpoints
| 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 |
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) |
| 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} |
| Method | Description | URL |
|---|---|---|
WS |
Server clock | ws://{host}:{port} |
WS |
Subscribe to updates | ws://{host}:{port}/{key} |
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) |
// 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,
})
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 |
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
},
})
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/*/*) |
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
},
})
Type-safe data operations with Go generics. Click to expand code.
Retrieve a single item from the specified path
// 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)
Retrieve all items from a list path (glob pattern)
// 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)
}
Create or update an item at the specified path
// 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)
}
Add an item to a list (path must end with "/*")
// 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)
Remove item(s) at the specified path or glob pattern
// 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)
}
cfg := io.RemoteConfig{
Client: &http.Client{Timeout: 10 * time.Second},
Host: "localhost:8800",
SSL: false, // set to true for HTTPS
}
Fetch single item or list from remote server
item, err := io.RemoteGet[YourType](cfg, "path/to/item")
items, err := io.RemoteGetList[YourType](cfg, "path/to/items/*")
Create/update item or add to list on remote server
err := io.RemoteSet(cfg, "path/to/item", YourType{Field1: "value"})
err := io.RemotePush(cfg, "path/to/items/*", YourType{Field1: "new item"})
Delete item(s) from remote server (supports glob patterns)
// Delete single item or use glob pattern for multiple items
err := io.RemoteDelete(cfg, "path/to/item")
err := io.RemoteDelete(cfg, "items/*")
Real-time WebSocket subscriptions with Go generics
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 */ },
})
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.
Complete examples demonstrating various features. Click to expand code.
package main
import "github.com/benitogf/ooo"
func main() {
server := ooo.Server{}
server.Start("0.0.0.0:8800")
server.WaitClose()
}
Static mode, write/read/delete filters, and API key authentication
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()
}
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()
}
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)
}
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)
}
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)
}
Subscribe to multiple list paths with different types using WaitGroup synchronization
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()
}
HTTP client operations with RemoteGet, RemoteSet, RemotePush, RemoteDelete
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")
}
Demonstrates `server.Endpoint()` for registering custom HTTP endpoints with typed schemas visible in the storage explorer UI.
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()
}
// 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()
}
// 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()
}
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()
}
Capped collections with strict schema validation on writes
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()
}
PostgreSQL storage with custom HTTP endpoints, JSONB field queries, and range queries by timestamp.
// 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()
}
Multi-instance synchronization using the pivot package for distributed ooo servers
// 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()
}
// 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()
}
Demonstrates using the proxy package to expose routes from remote OOO servers with path remapping.
// 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 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.
Related projects and integrations
Core server - state management with WebSocket/REST API.
Persistent storage adapter using LevelDB.
JavaScript client with reconnecting WebSocket.
JWT authentication middleware.
Full-stack boilerplate (Go + React).
PostgreSQL adapter for large-scale storage.
Multi-instance synchronization (AP distributed).