Handlers & data
A route loads data with HTTP-verb handlers. get runs on GET, post runs on POST. Both take the args bag and return data that the page's default export renders.
A page route with a get handler must export GetOutput — the type its get returns and its default renders.
import type { GetFunction } from 'camelon';
export type GetInput = { query: { name?: string } };
export type GetOutput = { greeting: string };
export const get: GetFunction<GetInput> = ({ query }) => {
return { greeting: `Hello, ${query.name ?? 'world'}` };
};
export default function Data({ greeting }: GetOutput) {
return (
<div class="d-card">
<strong safe>{greeting}</strong>
<p>Try adding <code>?name=Ada</code> to the URL above.</p>
</div>
);
}export type GetInput = { query: { name?: string } };
export type GetOutput = { greeting: string };
export const get: GetFunction<GetInput> = ({ query }) => {
return { greeting: `Hello, ${query.name ?? 'world'}` };
};
export default function Page({ greeting }: GetOutput) {
return <h1 safe>{greeting}</h1>;
}
The args bag
Every handler gets the same object (middleware gets a subset — no signals/stream/db/logger). There is no req, res, or cookies. Read through request, write through response, scratch on ctx, manage state through session.
| Key | What it is |
|---|---|
params |
path params (:id, :splat) |
query |
parsed query string |
body |
parsed request body |
signals |
Datastar state ({} if not a reactive request) |
files |
multipart uploads by field; .data is a Uint8Array |
request |
raw read: .url .headers .method .signal, .json() .text() .formData() |
response |
.status(code), .header(name, val) |
ctx |
middleware → handler scratch |
stream |
server → client SSE channel |
session |
signed cookie store; auto-commits on change |
db |
your injected client, typed from Register['db'] |
logger |
request-bound; carries { requestId, method, path } |
Return values
A page route always renders HTML. A resource route returns a string for HTML, an object for JSON, or a Response for full control.
export const get: GetFunction = () => {
return { ok: true, items: [1, 2, 3] }; // → application/json
};
import type { GetFunction } from 'camelon';
export type GetOutput = { ok: boolean; items: string[] };
export const get: GetFunction = (): GetOutput => ({
ok: true,
items: ['apple', 'banana', 'cherry'],
});Logging
logger is request-bound. Every line it writes carries the same requestId, method, and path, so one request's logs correlate end-to-end — no manual threading of an id through your call stack.
export const post: PostFunction<PostInput> = ({ body, logger }) => {
logger.info('order received', { sku: body.sku });
// → { level: 'info', msg: 'order received', sku: '…', requestId: '…', method: 'POST', path: '/orders' }
};
Next: typed contracts.