Yeti - The Performance Platform for Agent-Driven Development. Schema-driven APIs, real-time streaming, and vector search. From prompt to production.
The starter demo for yeti. Persistent counter, custom Rust endpoint, React frontend -- in under 20 lines of backend code.
demo-basic shows the two fundamental building blocks of every yeti application: a schema-defined table that gives you a full REST API with zero code, and a custom Rust resource that lets you add arbitrary server-side logic. A React/Vite frontend ties them together into an interactive UI with syntax-highlighted source panels so you can see exactly what powers each feature.
Most "hello world" demos stop at printing a string. demo-basic goes further -- it demonstrates the core developer loop for yeti applications:
- Schema to API in one file -- define a GraphQL type, get REST endpoints, SSE streaming, MQTT pub/sub, and MCP tools. No controllers, no routes, no boilerplate.
- Custom logic in pure Rust -- the
resource!()macro turns a struct with handler methods into a compiled native endpoint. Seven lines of code, sub-millisecond response times. - Persistent state out of the box -- the counter value survives server restarts. RocksDB storage is automatic from the schema. No database setup, no migrations, no ORM.
- Frontend build integration -- Vite builds are triggered automatically by yeti on first load. Static files are served with SPA routing. Hot module replacement works in dev mode.
- Public access control -- the
@export(public: [read, update])directive makes the counter table accessible without authentication, declared directly in the schema.
cd ~/yeti/applications
git clone https://github.com/yetirocks/demo-basic.gitRestart yeti. The Rust resource compiles automatically on first load (~2 minutes) and is cached for subsequent starts (~10 seconds). The Vite frontend builds on first request.
https://localhost:9996/demo-basic/
The interactive UI shows a counter panel with increment/decrement buttons and a greeting panel with a "Call /greeting" button. Each panel displays the source code (GraphQL schema and Rust resource) that powers it.
curl -s -X PUT https://localhost:9996/demo-basic/api/TableName/main-counter \
-H "Content-Type: application/json" \
-d '{"id": "main-counter", "count": 1}'Response:
{
"id": "main-counter",
"count": 1
}curl -s https://localhost:9996/demo-basic/api/TableName/main-counterResponse:
{
"id": "main-counter",
"count": 1
}The value persists across server restarts -- it is stored in RocksDB, not in memory.
curl -s https://localhost:9996/demo-basic/api/greetingResponse:
{
"greeting": "Hello, World!"
}This response is generated by resources/greeting.rs -- a compiled Rust plugin, not an interpreted script.
curl -s "https://localhost:9996/demo-basic/api/TableName?limit=10"Response:
[
{
"id": "main-counter",
"count": 1
}
]# SSE -- server-sent events (use Ctrl+C to stop)
curl -s --max-time 30 "https://localhost:9996/demo-basic/api/TableName?stream=sse"Open a second terminal and update the counter -- the SSE stream will emit the change immediately.
Browser / curl / AI Agent
|
+-- GET /demo-basic/ -----------> Static files (React/Vite SPA)
+-- GET/PUT /api/TableName/{id} ----> Schema-driven REST (RocksDB)
+-- GET /api/greeting --------------> Custom Rust resource (plugin)
+-- GET /api/TableName?stream=sse --> Real-time SSE stream
+-- POST /demo-basic/mcp ------> Auto-generated MCP tools
|
v
+------------------------------------------+
| demo-basic |
| +------------+ +-----------+ |
| | TableName | | Greeting | |
| | (RocksDB) | | (Rust) | |
| +------------+ +-----------+ |
| Schema-driven resource!() |
| REST + SSE + MQTT macro endpoint |
+------------------------------------------+
|
v
Yeti (embedded RocksDB, plugin compiler, static file server)
Schema path: GraphQL type definition -> yeti schema loader -> REST routes + SSE + MQTT + MCP auto-generated -> RocksDB storage.
Plugin path: Rust source file -> yeti plugin compiler -> wasm32-wasip2 component -> loaded at startup -> routed by name.
Static path: Vite source -> npm run build (auto-triggered) -> web/ directory -> served at /demo-basic/ with SPA fallback.
The counter is a schema-defined table with automatic REST endpoints. No handler code exists for CRUD operations -- they are generated entirely from the GraphQL schema:
type TableName @table(database: "demo-basic") @export(public: [read, update]) {
id: ID! @primaryKey
count: Int!
}This single type definition produces:
| Endpoint | Method | Description |
|---|---|---|
/demo-basic/api/TableName |
GET | List all records (with ?limit=N) |
/demo-basic/api/TableName |
POST | Create a new record |
/demo-basic/api/TableName/{id} |
GET | Read a single record |
/demo-basic/api/TableName/{id} |
PUT | Update a record |
/demo-basic/api/TableName/{id} |
DELETE | Delete a record |
/demo-basic/api/TableName?stream=sse |
GET | Real-time change stream |
The @export(public: [read, update]) directive allows unauthenticated GET and PUT requests. Create and delete still require authentication in production.
The greeting endpoint demonstrates the resource!() macro -- yeti's minimal syntax for custom server-side logic:
use yeti_sdk::prelude::*;
resource!(Greeting {
get => json!({"greeting": "Hello, World!"})
});This compiles to a native Rust plugin that responds at /demo-basic/api/greeting. The resource!() macro supports all HTTP methods (get, post, put, delete) and has access to the full Request and ExtensionContext when needed:
resource!(Greeting {
get(request, ctx) => {
// Access headers, query params, tables, etc.
json!({"greeting": "Hello, World!"})
}
});The frontend is a React/TypeScript application built with Vite. It provides:
- Counter panel -- increment/decrement buttons that call
PUT /api/TableName/{id}and display the current count - Greeting panel -- a button that calls
GET /api/greetingand displays the JSON response - Source panels -- syntax-highlighted displays of the GraphQL schema and Rust resource code, so the demo is self-documenting
- Responsive layout -- two-column grid on desktop, single column on mobile
The frontend builds automatically on first load via yeti's static file build integration. During development, run npm run dev in the source/ directory for hot module replacement.
The @export directive on the schema automatically enables real-time streaming:
# SSE -- server-sent events
curl -s --max-time 30 "https://localhost:9996/demo-basic/api/TableName?stream=sse"
# MQTT -- subscribe to changes
mosquitto_sub -t "demo-basic/TableName" -h localhost -p 8883When the counter is updated via REST or the UI, all subscribers receive the change immediately.
MCP tools for table operations are auto-generated from the @export schema. Any MCP-compatible agent (Claude Code, Cursor, Windsurf) can discover and use them via the standard MCP protocol at POST /demo-basic/mcp.
| Field | Type | Description |
|---|---|---|
id |
ID! (primary key) | Unique record identifier (e.g., "main-counter") |
count |
Int! | The counter value |
Public access: read, update (via @export(public: [read, update]))
Storage: embedded RocksDB, database "demo-basic"
App configuration lives in Cargo.toml under [package.metadata.app]. There is no separate config.yaml or services.yaml -- yeti reads everything it needs from the crate manifest.
[package]
name = "demo-basic"
version = "1.0.0"
description = "Simple counter with persistent state and a custom Rust greeting endpoint"
[package.metadata.app]
schemas = "schemas/basic.graphql"
resources = "resources/*.rs"
static = { path = "web", source = "source", spa = true, build = "npm install && npm run build" }| Key | Purpose |
|---|---|
schemas |
GraphQL schema file. Each @table type becomes a RocksDB-backed table with auto-generated REST/SSE/MQTT/MCP endpoints. |
resources |
Glob pattern for Rust resource files. Each file is compiled into a native plugin and routed by struct name. |
static.path |
Directory containing built static assets (output of npm run build). |
static.source |
React/Vite source directory used when static.path is empty. |
static.spa |
Enables SPA routing -- unknown paths serve index.html with a 200 status. |
static.build |
Build command yeti runs from source when no built assets exist. |
Pre- and post-request shell hooks can be declared under [package.metadata.app.hooks]:
[package.metadata.app.hooks]
pre_request = ["./hooks/validate.sh"]
post_request_failure = ["./hooks/alert.sh"]cd ~/yeti/applications/demo-basic/source
# Install dependencies
npm install
# Start Vite dev server with HMR (proxied through yeti)
npm run dev
# Production build (also triggered automatically by yeti)
npm run builddemo-basic uses yeti's declarative public access model. The @export(public: [read, update]) directive on the TableName schema allows unauthenticated read and update operations. No auth configuration is needed for this demo.
In development mode, all endpoints are accessible without authentication regardless of directives. In production:
| Endpoint | Auth Required | Notes |
|---|---|---|
GET /api/TableName |
No | Public read via schema directive |
GET /api/TableName/{id} |
No | Public read via schema directive |
PUT /api/TableName/{id} |
No | Public update via schema directive |
POST /api/TableName |
Yes | Create not listed in public access |
DELETE /api/TableName/{id} |
Yes | Delete not listed in public access |
GET /api/greeting |
No | Custom resources are public by default in dev |
Static files (/) |
No | Always public |
To add authentication, declare a [package.metadata.auth] section in Cargo.toml -- supported methods, JWT settings, OAuth providers, and role rules all live there. UI apps can drop in the shared Login.tsx page and useAuth hook to gate the SPA without writing custom code.
demo-basic/
├── Cargo.toml # App configuration under [package.metadata.app]
├── schemas/
│ └── basic.graphql # Counter table schema with @export directive
├── resources/
│ └── greeting.rs # Custom Rust greeting endpoint (7 lines)
├── source/ # React/Vite frontend source
│ ├── package.json # Dependencies (React 18, Vite 5, TypeScript, highlight.js)
│ ├── tsconfig.json
│ ├── vite.config.ts
│ └── src/
│ ├── main.tsx # Entry point
│ ├── App.tsx # Thin shell -- wires auth gate + page
│ ├── api.ts # Fetch helpers
│ ├── types.ts # Shared TypeScript types
│ ├── utils.ts # JSON syntax highlighting utility
│ ├── components/
│ │ └── Footer.tsx # Shared UI primitives
│ ├── hooks/
│ │ └── useAuth.ts # Auth state hook (template)
│ ├── pages/
│ │ ├── BasicPage.tsx # Counter + greeting panels with source display
│ │ └── Login.tsx # Configurable login page (template)
│ └── styles/
│ ├── _vars.css # Per-app brand colors and shared tokens
│ ├── yeti.css # Canonical Yeti stylesheet
│ └── index.css # App-specific overrides
└── web/ # Built static assets (auto-generated)
The src/ layout is the standard yeti UI app structure: a thin App.tsx, root utility files (api.ts, types.ts, utils.ts), shared UI in components/, hooks in hooks/, page components in pages/, and stylesheets in styles/. yeti.css is the canonical stylesheet shared across all yeti apps; _vars.css holds this app's brand tokens; index.css carries app-specific overrides.
| demo-basic | Traditional Approach | |
|---|---|---|
| Backend code | 7 lines (greeting.rs) + 6 lines (schema) | Express/Flask route handlers, ORM models, migrations |
| Database setup | Zero -- declared in schema, auto-provisioned | Install DB, create database, run migrations, configure connection |
| REST API | Auto-generated from schema | Hand-written controllers for each CRUD operation |
| Real-time | Auto-generated SSE + MQTT from @export |
WebSocket server, custom event wiring, external broker |
| MCP tools | Auto-generated from schema | Separate MCP server process with hand-written tool definitions |
| Auth model | Declarative in schema (public: [read, update]) |
Middleware chains, role checks in every handler |
| Frontend build | Auto-triggered by yeti on first load | Separate build step, CI/CD pipeline, deployment config |
| Deployment | Drop folder in ~/yeti/applications/ |
Dockerfile, docker-compose, reverse proxy, process manager |
| Cold start | ~2 min compile (cached: ~10 sec) | Depends on runtime, dependency install, DB connection |
| Runtime | Native Rust binary (sub-ms responses) | Interpreted (Node.js/Python) or JIT (JVM) |
Built with Yeti | The Performance Platform for Agent-Driven Development
