ENGINEHANDBOOK
← all posts

Your first app in 15 minutes

tutorialrestgetting-started

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.

pilserlabs · open in main window