@better-state/server
v0.4.0
Published
Better-State server — shared state primitive for developers
Maintainers
Readme
@better-state/server
Self-hosted real-time state server. SQLite-backed, zero-config, embeddable.
Part of Better-State — a shared state primitive for developers.
Quick Start
npx @better-state/serverThat's it. The server starts on http://localhost:3001, auto-creates a default namespace, and prints your API key. Data is stored in .better-state/ in your working directory.
Open http://localhost:3001/playground for an interactive counter demo.
Installation
npm install @better-state/server
# or
pnpm add @better-state/serverAs a global CLI
npm install -g @better-state/server
better-stateProgrammatic Usage
The server exports a factory function for embedding in your own application or tests:
import { createBetterStateServer } from '@better-state/server/server'
const server = await createBetterStateServer({
port: 3001,
corsOrigin: 'https://myapp.com',
})
await server.start()
console.log(`Listening on port ${server.getPort()}`)
// Graceful shutdown
process.on('SIGTERM', async () => {
await server.stop()
process.exit(0)
})Options
| Option | Default | Description |
|--------|---------|-------------|
| port | 3001 / PORT env | Server port. Use 0 for ephemeral (tests). |
| corsOrigin | * / CORS_ORIGIN env | Allowed CORS origins |
| store | SQLite (file) | Custom StorageAdapter instance |
| maxHttpJsonBytes | 256kb | Max REST body size |
| maxWsJsonBytes | 256kb | Max WebSocket payload size |
| maxMutationsPerBatch | 100 | Max mutations per mutate call |
| maxKeyLength | 200 | Max state key length |
| logStartup | true | Print startup banner |
Custom Storage
import { createBetterStateServer } from '@better-state/server/server'
import { SqliteAdapter } from '@better-state/server/storage'
// In-memory (great for tests)
const store = new SqliteAdapter({ memory: true })
// Custom file path
const store = new SqliteAdapter({ dbPath: '/data/my-app.db' })
const server = await createBetterStateServer({ store })Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| PORT | 3001 | Server port |
| CORS_ORIGIN | * | Allowed origins |
| DATABASE_PATH | .better-state/state.db | SQLite database path |
| LOG_LEVEL | info | debug / info / warn / error |
| LOG_PRETTY | false | Pretty-print JSON logs |
| STUDIO_PASSWORD | (none) | Password for Studio dashboard |
| MAX_HTTP_JSON_BYTES | 256kb | Max REST body size |
| MAX_WS_JSON_BYTES | 256kb | Max WebSocket payload size |
| MAX_MUTATIONS_PER_BATCH | 100 | Max mutations per batch |
| MAX_KEY_LENGTH | 200 | Max state key length |
REST API
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | /api/v1/health | Health check ({ status: "ok" }) |
| GET | /api/v1/namespaces | List all namespaces |
| POST | /api/v1/namespaces | Create namespace (returns API key) |
| GET | /api/v1/states?namespace=ID | List states in a namespace |
| GET | /api/v1/states/:key/history?namespace=ID | Paginated event history |
Create a namespace
curl -X POST http://localhost:3001/api/v1/namespaces \
-H 'Content-Type: application/json' \
-d '{"name": "my-project"}'{
"id": "uuid",
"name": "my-project",
"apiKey": "raw-api-key-save-this",
"created_at": 1740567600000
}WebSocket Protocol
Clients connect via Socket.IO and communicate through these events:
| Event | Direction | Description |
|-------|-----------|-------------|
| auth | client -> server | Authenticate with API key |
| subscribe | client -> server | Subscribe to state keys |
| mutate | client -> server | Send mutations (with optimistic concurrency) |
| state:init | server -> client | Initial state value after subscribe |
| state:update | server -> client | Broadcast after mutation |
| mutate:ack | server -> client | Mutation accepted |
| mutate:error | server -> client | Mutation rejected (conflict, validation) |
Studio Dashboard
The server includes a built-in admin dashboard at /studio:
- View all namespaces and state objects
- Real-time metrics (connections, mutations/sec)
- Export state snapshots
- Protected by
STUDIO_PASSWORDwhen set
Architecture
- Express 4 for REST + static serving
- Socket.IO for WebSocket transport
- SQLite (via better-sqlite3) for persistence
- Event log — every mutation is appended, state is replayed
- Optimistic concurrency — version-based conflict detection with automatic client resync
- Graceful shutdown —
SIGTERM/SIGINThandlers drain connections and flush writes
License
MIT
