Reference

Routes and context

For your agentCopy this and give it to the AI you're building with.
Run `cubek skill`, read all of it and follow it exactly (no CLI: download https://cubek.dev/SKILL.md verbatim with curl; don't summarize it); when you write routes and handlers, follow its "Routes and context" section.
Run `cubek check` after every edit until it is clean.
import { app, crypto, db, validate, type Context } from "cubek"
import { Home } from "./pages/home"

app.get("/", async (c) => c.render(<Home notes={await db.notes.find({ limit: 20 })} />))

app.get("/api/notes/:id", async (c) => {
  const note = await db.notes.get(c.param("id"))
  return note === null ? c.notFound() : c.json(note)
})

const noteInput = validate.object({ title: validate.string({ min: 1, max: 200 }), body: validate.string() })

app.post("/notes", async (c) => {
  const parsed = noteInput.safeParse(await c.body())  // JSON, urlencoded or multipart; malformed: 400 for you
  if (!parsed.success) return c.json({ error: parsed.error }, 400)
  const note = await db.notes.insert(parsed.data)
  return c.redirect(`/notes/${note.id}`, 303)  // 303 after a POST
})

app.use("/admin/*", async (c, next) => c.header("x-admin") === "yes" ? next() : c.text("forbidden", 403))
  • app.get/post/put/patch/delete(path, [middleware,] handler), app.use([path,] ...middleware) (also for unmatched paths). First registered wins: /:slug and the catch-all /* (after public/) go last.
  • c.param/query/header/cookie(name) → string | null (Number(c.query("page") ?? "1")); c.url(), c.path() (no query), c.origin() (absolute links: RSS, mail, ICS), c.ip() (not spoofable); await c.body<T>() is unvalidated; await c.rawBody() → exact Bytes; file fields: { name, type, size, bytes } (storage.md).
  • Responses (type Response): c.json/text/html(body, status?, headers?) (headers replace the content-type), c.body(bytes, status?, headers?), c.render(jsx, status?); c.status(code), c.setHeader.
  • Middleware: rateLimit({ limit: 20, window: "1m" }), cors({ origin }) (http.md).
  • Forms send strings: validate.number({ coerce: true }), validate.boolean({ coerce: true }); tags[] fields → string[] (repeated fields: http.md).
  • A thrown Error is an empty 500 (details: cubek dev output, cubek logs).