Examples
The interactive demos throughout these docs are live — each one a real camelon route answering its own request. Open the Network tab to watch.
Each app below is a complete, runnable camelon project under examples/ in the repo. Run any of them with pnpm dev in its directory (deno task dev for the Deno one).
counter
The smallest app. A Datastar counter: signals on the client, two resource routes that patch count back over SSE. examples/counter.
export default function Counter() {
return (
<div class="d-row" signals='{"count": 0}'>
<button class="d-btn" on:click="@post('/counter/decrement')">−</button>
<output class="d-num" text="$count">0</output>
<button class="d-btn" on:click="@post('/counter/increment')">+</button>
</div>
);
}import type { PostFunction } from 'camelon';
export type PostInput = { signals: { count: number } };
export const post: PostFunction<PostInput> = ({ stream, signals }) => {
stream.patchSignals({ count: signals.count + 1 });
};import type { PostFunction } from 'camelon';
export type PostInput = { signals: { count: number } };
export const post: PostFunction<PostInput> = ({ stream, signals }) => {
stream.patchSignals({ count: signals.count - 1 });
};head
Per-route head export — a route overrides the document <title> and adds its own styles, merged tag-by-tag with the document head. examples/head.
import type { HeadFunction } from 'camelon';
export const head: HeadFunction = () => (
<>
<title>Head override — camelon</title>
<style>{`.d-head{background:linear-gradient(135deg,#6366f1,#a855f7);color:#fff}.d-head p,.d-head code{color:#eef;background:rgba(255,255,255,.15)}`}</style>
</>
);
export default function Head() {
return (
<div class="d-card d-head">
<strong>This route set its own <title> + background</strong>
<p>
via a <code>head</code> export — camelon merged it into the document head. The gradient
comes from that head's <code><style></code>.
</p>
</div>
);
}chat
Streaming SSE chat. The response route appends words to a message bubble with patchElements(html, { selector, mode: 'append' }), then returns the final element. examples/chat.
export default function Chat() {
return (
<div class="d-card d-chat">
<div id="msgs" class="d-msgs"></div>
<form class="d-row" signals='{"prompt": ""}' on:submit="@get('/chat/response')">
<input class="d-input" placeholder="Say something…" bind:prompt="" aria-label="message" />
<button class="d-btn d-wide" type="submit">Send</button>
</form>
</div>
);
}import type { GetFunction } from 'camelon';
export type GetInput = { signals: { prompt: string } };
async function* reply(prompt: string): AsyncGenerator<string> {
const text = `you said “${prompt}”. this reply streams back one word at a time.`;
for (const word of text.split(' ')) {
await new Promise((r) => setTimeout(r, 70));
yield word;
}
}
export const get: GetFunction<GetInput> = async ({ signals, stream }) => {
const prompt = String(signals.prompt ?? '').trim();
if (!prompt) return;
stream.patchElements(await (<p class="d-msg d-me">{prompt}</p>), { selector: '#msgs', mode: 'append' });
stream.patchSignals({ prompt: '' });
const id = `r${Date.now()}`;
stream.patchElements(await (<p class="d-msg d-bot" id={id} />), { selector: '#msgs', mode: 'append' });
let acc = '';
for await (const word of reply(prompt)) {
acc = acc ? `${acc} ${word}` : word;
stream.patchElements(await (<p class="d-msg d-bot" id={id}>{acc}</p>));
}
}session
Login, logout, and a guarded dashboard. Cookie sessions, a _middleware.ts auth guard, and one-read flash messages. examples/session.
import { redirect } from 'camelon';
import type { PostFunction } from 'camelon';
export type PostInput = { body: { email: string; password: string } };
export const post: PostFunction<PostInput> = async ({ body, session }) => {
const user = await authenticate(body);
session.set('userId', user.id);
session.flash('toast', 'Welcome back'); // read once on the next page
throw redirect('/dashboard');
};import { redirect } from 'camelon';
import type { PostFunction } from 'camelon';
export const post: PostFunction = async ({ session }) => {
session.destroy(); // clears state, expires the cookie
throw redirect('/');
};db
A fruit store with a cart, backed by an injected client through the db arg. examples/db.
import type { GetFunction } from 'camelon';
import type { Fruit, FruitDb } from '../../db';
declare module 'camelon' {
interface Register {
db: FruitDb;
}
}
export type GetOutput = { fruits: Fruit[] };
export const get: GetFunction = ({ db }) => {
return { fruits: db.fruits() };
};
export default function Db({ fruits }: GetOutput) {
return (
<div class="d-card">
<strong>Fruit store</strong>
<ul class="d-list">
{fruits.map((f) => (
<li>
{f.emoji} {f.name} — {f.price}¢
</li>
))}
</ul>
</div>
);
}deno-counter
The counter app on Deno — Deno.serve(await createHandler(...)), no Express, no Node. Proof the core is runtime-agnostic. examples/deno-counter.
browser-counter
camelon's web-standard core running in a browser service worker — a portability demo, not a production target. See runtime targets. examples/browser-counter.