Your first app in 15 minutes
This walkthrough creates a complete app — table, validation, realtime UI, static hosting — against a running engine. No builds, no deployments, no consoles.
Prereq: an engine reachable at ENGINE (e.g. http://127.0.0.1:7070) and your board
id B. On pilserlabs.com the engine is already running; this guide works against any
instance, including one you embed in your own Rust binary.
1. Create the board
curl -s -X POST $ENGINE/mcp \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"apps.create","arguments":{"title":"Todo"}}}'
# -> {"result":{"board":"b_xxxxxxxxxxxxxxxx",...}}
export B=b_xxxxxxxxxxxxxxxx
The board id doubles as an owner bearer token (Authorization: Bearer $B) — the
simplest possible auth for bootstrapping.
2. Create a table with validation
curl -s -X POST "$ENGINE/api/srv/$B/tables/todos" \
-H "authorization: Bearer $B" -H 'content-type: application/json' \
-d '{
"schema": {
"type": "object",
"required": ["title"],
"properties": {
"title": {"type": "string", "maxLength": 200},
"done": {"type": "boolean"}
}
}
}'
Every insert is now validated server-side. Schema is optional per table — start loose, tighten later.
3. Insert and read records
curl -s -X POST "$ENGINE/api/srv/$B/tables/todos/submit" \
-H "authorization: Bearer $B" -H 'content-type: application/json' \
-d '{"payload":{"title":"ship the engine handbook","done":false}}'
# -> {"ok":true,"seq":1}
curl -s "$ENGINE/api/srv/$B/tables/todos/records?limit=20"
curl -s "$ENGINE/api/srv/$B/tables/todos/aggregate?op=count"
curl -s "$ENGINE/api/srv/$B/tables/todos/query?filter=%7B%22done%22%3Afalse%7D"
Records are JSON documents with a monotonic seq. Point reads are record?seq=,
patching is JSON-Patch (PATCH .../records/1), search is q= (BM25 full-text).
4. Make it real-time (one line)
const es = new EventSource(`${ENGINE}/api/srv/${B}/tables/todos/events/stream`);
es.onmessage = (e) => console.log('change:', JSON.parse(e.data));
Every insert/update/delete now pushes to every open tab. No polling, no sockets library, no extra service.
5. Add users (real auth, still no code)
# signup returns a JWT + session token scoped to THIS board only
curl -s -X POST "$ENGINE/api/srv/$B/auth/signup" -H 'content-type: application/json' \
-d '{"email":"dev@example.com","password":"correct horse"}'
Keys can be issued with roles (reader, writer, admin) and row scopes
(scope=customer_42 silently filters every read/write). Your API stops trusting
the client without writing middleware.
6. Ship a frontend
zip -r dist.zip dist/ # any static build output with index.html at root
curl -X POST https://pilserlabs.com/api/agent/entries \
-H "Authorization: Bearer $AGENT_KEY" -H "Origin: https://pilserlabs.com" \
-F slug=todo-demo -F kind=experiment -F status=live \
-F title="Todo demo" -F buttonLabel=open -F file=@dist.zip
Live at /p/todo-demo/, same-origin with the API — the EventSource URL above needs
no CORS configuration.
What you did NOT do
- provision a database, run migrations, or write connection strings
- deploy a backend process
- write auth middleware
- set up websockets infrastructure
- configure CI/CD to ship changes
That absence is the product. The next post covers what the engine does internally when you hit “submit”: the write path.