Skip to content

iii-hq/todo-app

Repository files navigation

Todo App — Real-time with iii

A real-time todo application built with iii, demonstrating how the browser becomes a first-class worker connected to the engine via a single WebSocket.

Todo App Screenshot

Architecture

┌─────────────────────┐        WebSocket         ┌──────────────────┐
│   React + HeroUI    │◄──────────────────────────►   iii Engine     │
│   (iii-browser-sdk) │       port 3111           │                  │
│   Browser Worker    │                           │  ┌────────────┐  │
└─────────────────────┘                           │  │ RBAC       │  │
                                                  │  │ Streams    │  │
┌─────────────────────┐        WebSocket          │  │ OTel       │  │
│   Node.js API       │◄──────────────────────────►  └────────────┘  │
│   (iii-sdk)         │       port 49134          │                  │
│   API Worker        │                           └──────────────────┘
└─────────────────────┘

The app consists of two workers connected to the same iii engine:

  • API Worker (apps/api) — A Node.js backend that registers CRUD functions and manages todo data through iii Streams.
  • Browser Worker (apps/website) — A React frontend using iii-browser-sdk that registers its own functions, triggers backend functions, and receives real-time stream updates — all over a single WebSocket connection.

Features Used

1. Worker Registration

Both the server and browser register as workers with the iii engine using registerWorker:

Server (apps/api/src/iii.ts):

import { Logger, registerWorker } from 'iii-sdk'

const iii = registerWorker('ws://localhost:49134', {
  workerName: 'api-worker',
})

Browser (apps/website/src/lib/iii.ts):

import { registerWorker } from 'iii-browser-sdk'

const iii = registerWorker('ws://localhost:3111')

2. Remote Functions

The API worker registers functions that can be invoked by any other worker (including the browser). Each function has a namespaced ID like todos::create:

fn(
  'todos::create',
  async (req: { title: string }): Promise<Todo> => {
    const id = crypto.randomUUID()
    const result = await todosStream.set({
      group_id: 'todos',
      item_id: id,
      data: { id, title: req.title, completed: false },
    })
    return result.new_value
  },
  { description: 'Create a new todo' },
)

Registered functions: todos::create, todos::list, todos::get, todos::toggle, todos::delete.

3. Function Invocation (trigger)

The browser calls backend functions directly using iii.trigger() — no REST endpoints, no fetch calls:

const { items } = await iii.trigger<{}, { items: Todo[] }>({
  function_id: 'todos::list',
  payload: {},
})

4. Streams (Real-time State)

Todos are stored in an iii Stream — a persistent, observable data structure backed by a file-based KV store. The TodoStream class wraps stream operations (get, set, list, update, delete):

export class TodoStream implements IStream<Todo> {
  async set(args) {
    return iii.trigger({ function_id: 'stream::set', payload: { ...args, stream_name: 'todo' } })
  }
  // get, list, update, delete...
}

When any stream data changes, the engine automatically pushes the change event to all subscribers.

5. Stream Triggers (Real-time Updates in the Browser)

The browser registers a function and binds a stream trigger to it. Whenever a todo is created, updated, or deleted, the engine invokes this browser function in real time:

const funcRef = iii.registerFunction({ id: 'ui::on-todo-change' }, async (input: StreamChangeEvent) => {
  switch (input.event.type) {
    case 'create':
      setTodos((prev) => [...prev, todo])
      break
    case 'update':
      setTodos((prev) => prev.map((t) => (t.id === input.id ? todo : t)))
      break
    case 'delete':
      setTodos((prev) => prev.filter((t) => t.id !== input.id))
      break
  }
  return {}
})

iii.registerTrigger({
  type: 'stream',
  function_id: funcRef.id,
  config: { stream_name: 'todo', group_id: 'todos' },
})

6. RBAC (Role-Based Access Control)

The public-facing worker on port 3111 has RBAC enabled. An auth function creates a session for each connecting browser client and controls what the client is allowed to do:

fn('todo-project::auth-function', async (input: AuthInput): Promise<AuthResult> => {
  const sessionId = crypto.randomUUID()
  return {
    allowed_functions: [],
    forbidden_functions: [],
    allow_function_registration: true,
    allowed_trigger_types: ['stream'],
    function_registration_prefix: sessionId,
    // ...
  }
})

The engine config restricts which functions are exposed to browser clients:

rbac:
  auth_function_id: todo-project::auth-function
  expose_functions:
    - match("todos::create")
    - match("todos::list")
    - match("todos::get")
    - match("todos::delete")
    - match("todos::toggle")

7. OpenTelemetry (Observability)

The engine has built-in OpenTelemetry support for traces, metrics, and logs — configured via iii-config.yaml:

- class: modules::observability::OtelModule
  config:
    enabled: true
    service_name: iii-test
    metrics_enabled: true
    logs_enabled: true
    sampling_ratio: 1.0

8. Structured Logging

The API worker uses the iii Logger for structured log output:

import { Logger } from 'iii-sdk'
const logger = new Logger()

logger.info('Toggling todo', { id: req.id })
logger.warn('Todo not found', { id: req.id })

Using iii-browser-sdk

This project uses iii-browser-sdk (v0.10.0-beta.2) to turn the React frontend into a full iii worker running in the browser. The browser SDK provides the same API as the server SDK (registerFunction, trigger, registerTrigger) but with zero Node.js dependencies and a browser-native WebSocket transport.

Key resources:

Resource Link
npm package npmjs.com/package/iii-browser-sdk
Documentation iii.dev/docs
Browser guide Use iii in the Browser
Browser SDK API reference iii.dev/docs/api-reference/sdk-browser
Server SDK (iii-sdk) npmjs.com/package/iii-sdk
iii Engine github.com/iii-hq/iii
Examples github.com/iii-hq/iii-examples

Imports used in this project:

Import Purpose
iii-browser-sdk Core SDK — registerWorker to connect to the engine
iii-browser-sdk/stream StreamChangeEvent type for handling real-time stream updates
iii-sdk Server-side SDK — registerWorker, Logger, AuthInput, AuthResult
iii-sdk/stream Server-side stream types — IStream, StreamSetInput, etc.

Setup

Prerequisites

  • Node.js >= 22.12.0
  • pnpm 10.33.0+ (managed via packageManager field)
  • Vite+ (vp) — install globally with npm i -g vite-plus
  • iii Engine — install from github.com/iii-hq/iii
  • Bun — used to run the API worker (bun run --watch)

1. Install dependencies

vp install

2. Start the iii engine

From the apps/api directory, start the engine with the project config:

cd apps/api
iiii --config iii-config.yaml

This boots the engine with:

  • Internal worker port on 49134 (for the API worker)
  • Public worker port on 3111 with RBAC (for browser clients)
  • Stream module on 3112 with file-based KV persistence
  • OTel module for observability
  • Exec module that auto-runs pnpm dev to start the API worker

3. Start the website

In a separate terminal, from the root:

vp run dev

This starts the Vite dev server for the React frontend.

4. Open the app

Navigate to http://localhost:5173 to see the todo app. Every change is reflected in real time across all connected browser tabs.

Project Structure

todo-app/
├── apps/
│   ├── api/                    # Backend worker (Node.js + iii-sdk)
│   │   ├── iii-config.yaml     # iii engine configuration
│   │   ├── src/
│   │   │   ├── main.ts         # Entry point — imports all routes
│   │   │   ├── iii.ts          # Worker registration + logger
│   │   │   ├── lib/
│   │   │   │   ├── decorators.ts   # fn() helper for function registration
│   │   │   │   └── rbac.ts         # Auth function for browser RBAC
│   │   │   └── routes/
│   │   │       ├── todos.stream.ts   # TodoStream — wraps iii stream ops
│   │   │       ├── todos.create.ts   # Create a todo
│   │   │       ├── todos.list.ts     # List all todos
│   │   │       ├── todos.get.ts      # Get a todo by ID
│   │   │       ├── todos.toggle.ts   # Toggle completed status
│   │   │       └── todos.delete.ts   # Delete a todo
│   │   └── package.json
│   └── website/                # Frontend worker (React + iii-browser-sdk)
│       ├── src/
│       │   ├── App.tsx         # Todo UI (HeroUI + Tailwind)
│       │   ├── main.tsx        # React entry point
│       │   ├── hooks/
│       │   │   └── use-todos.ts    # Real-time todo state via iii
│       │   └── lib/
│       │       └── iii.ts          # Browser worker registration
│       └── package.json
├── package.json                # Monorepo root
├── pnpm-workspace.yaml         # pnpm workspace config
└── vite.config.ts              # Vite+ root config (lint, fmt)

Tech Stack

Layer Technology
Engine iii
Server SDK iii-sdk
Browser SDK iii-browser-sdk
Frontend React 19, HeroUI, Tailwind CSS
Tooling Vite+, pnpm, TypeScript
API Runtime Bun
Observability OpenTelemetry (built into iii)

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors