Chapter II · The framework
Ajo Kit
Routes are files. Data is server truth.
A full-stack framework for Ajo: file routes and nested layouts, server loaders and form actions, SQLite migrations through Kysely, and live route updates over server-sent events. Vite runs development; the build stages a closed module graph for the engine.


What Ajo Kit gives you
- Loaders are server truthhandler.ts returns the data a page renders; parent() reads what its layouts loaded.
- SQLite and migrationsKysely over SQLite, with up and down migrations from your App and its plugins in one history.
- Live routesLoaders track topics, actions emit them, and open pages receive a fresh route payload over SSE.
- Routes follow filespage.tsx and layout.tsx under src/ become routes, with dynamic segments, catch-alls and groups.
- Actions for writesNamed POST handlers on the route, with the action() helper for forms and validation through Valibot.
- Built for the enginekit build stages a closed module graph and a descriptor of what the App needs to run.
In practice
src/notes/[id]/handler.ts
Status Published on npm. Pre-1.0: its API is still being refined.
ajo-kit0.5.0File routes, loaders, actions, SQLite migrations and live updates.
ajo-kit-auth0.7.1Sessions, CSRF, guards, scoped tokens, passkeys and invitations.
ajo-kit-mail0.4.1One validation boundary for mail, with SMTP, HTTP and capture transports.
import { Missing, type Request } from 'ajo-kit'
import { db } from '/src/data'
export async function page(req: Request) {
req.track?.(`note:${req.params.id}`)
const note = await db().selectFrom('notes')
.select(['id', 'text']).where('id', '=', Number(req.params.id))
.executeTakeFirst()
if (!note) throw new Missing()
return { note }
}
export async function head(req: Request) {
return { title: `Note ${req.params.id}` }
}