Project structure
A scaffolded Kovo project is a small authenticated app. Here is the shape create-kovo writes:
my-app/
|-- .env # generated local CSRF/auth secret, gitignored
|-- .env.example # deployment secret template
|-- .github/workflows/ci.yml
|-- .kovo/ # generated endpoint posture + agent docs mirror
|-- .gitignore
|-- AGENTS.md # generated Kovo rules for coding agents
|-- CLAUDE.md # points agent tools at AGENTS.md
|-- README.md
|-- kovo.config.ts # production build preset
|-- package.json
|-- vite.config.ts # Kovo project config; the pinned implementation runner stays internal
|-- src/
| |-- _kovo/
| | |-- app-runtime-db-options.ts # validated schema and boot/CLI database options
| | `-- app-runtime-db.ts # framework-owned database and auth boundary
| |-- app.tsx # routes, layout, and the single app.assemble() call
| |-- app.test.ts # focused app smoke test
| |-- auth.ts # typed session, guard, CSRF, and sanitized auth bindings
| |-- db.ts # app-facing read-only Drizzle value
| |-- endpoint-posture.test.ts
| |-- kovo.ts # the single defineKovo() provider/config contract
| |-- mutations.ts # guarded add-contact mutation
| |-- queries.ts # typed contact query
| |-- schema.ts # app tables plus Better Auth tables
| |-- styles.css # document CSS
| |-- theme.ts # typed theme tokens
| `-- components/
| |-- auth-forms.tsx # sign-in/sign-out forms
| `-- contacts.tsx # query-backed contact region and add form
`-- tsconfig.json # TypeScript config written by the scaffoldThe SQLite dialect swaps in package.sqlite.json, src/db.sqlite.ts, src/schema.sqlite.ts,
src/auth.sqlite.ts, and README.sqlite.md at scaffold time. The public file names stay the same
inside your generated app.
The app entry#
src/kovo.ts captures providers and app-wide config once:
import { createMemoryVersionedClientModuleRegistry } from '@kovojs/server/client-modules';
import { defineKovo } from '@kovojs/server';
declare const appCsrf: any;
declare const appRuntimeDbProvider: () => unknown;
declare const appSessionProvider: (request: Request) => unknown;
export const app = defineKovo({
appId: '61cc6f90-8870-4dcf-977f-2df98af8cd93',
auth: appSessionProvider,
clientModules: createMemoryVersionedClientModuleRegistry(),
csrf: appCsrf,
db: appRuntimeDbProvider,
});src/app.tsx declares routes and closes the exact handle inventory:
import { app } from './kovo.js';
declare const addContact: any;
declare const AppLayout: any;
declare const contactsQuery: any;
declare const healthEndpoint: any;
declare const homeRoute: any;
declare const loginRoute: any;
declare const appSignIn: any;
declare const appSignOut: any;
const signInMutation = app.integrateMutation(appSignIn);
const signOutMutation = app.integrateMutation(appSignOut);
export default app.assemble({
endpoints: [healthEndpoint],
layouts: [AppLayout],
mutations: [addContact, signInMutation, signOutMutation],
queries: [contactsQuery],
routes: [homeRoute, loginRoute],
});create-kovo generates this UUIDv4 once. Keep it stable across replicas and deployments of the same
app; generate a different UUIDv4 for every distinct app. Production apps with live-target renderers
must declare it. Kovo binds live-target reconstruction tokens to this identity so one app cannot
replay a descriptor into another app with the same render plan, including when the apps run in
separate processes or isolates. Keep the CSRF or KOVO_LIVE_TARGET_SECRET signing secret unique to
the app as well.
The home route redirects unauthenticated requests to /login; the login route renders the auth
form. The full scaffold passes appSessionProvider to defineKovo(), registers the health endpoint,
and uses the same app.layout() and stylesheet declaration on both pages.
Auth and secrets#
src/auth.ts wires Better Auth through @kovojs/better-auth:
betterAuthCsrfFromEnvironment({ field: 'csrf' })returns an opaque CSRF configuration. The signing secret and session-binding callback stay package-owned.createAppAuthBindings()returns only a sanitized session provider plus sign-in and sign-out mutations. The raw Better Auth instance and writable system database never enter app source.- CSRF tokens bind to a framework-owned anonymous id before login and to the session id after login.
- Boot-only
seedDemoUser()creates thedemo@example.comcredential with the randomKOVO_DEMO_PASSWORD. It creates no session; sign-in still requires the CSRF-protected mutation.
create-kovo writes a fresh KOVO_CSRF_SECRET and local-only KOVO_DEMO_PASSWORD into .env and
refuses to let the app run with the CSRF placeholder. In production, set BETTER_AUTH_SECRET or
KOVO_CSRF_SECRET through the platform's secret store, set BETTER_AUTH_URL to the canonical HTTPS
origin, and leave KOVO_DEMO_PASSWORD unset.
Data, queries, and mutations#
src/schema.ts declares the app table and the Better Auth tables. The contact table is annotated so
the compiler can connect writes to query refreshes. The generated _kovo runtime creates the
database and performs fixed boot-only seeding; src/db.ts exposes its read-only app value.
src/queries.ts owns the contact read; src/mutations.ts owns the guarded add-contact write.
When adding product data, keep the same separation:
- Add the table and domain metadata in
schema.ts. - Seed or connect storage in
db.ts. - Add typed reads in
queries.ts. - Add guarded writes in
mutations.ts. - Render the data from
components/.
That path keeps the compiler's query/write extraction readable and gives tests one clear place to assert each behavior.
Agent helper files#
The scaffold writes AGENTS.md, a CLAUDE.md pointer, and .kovo/docs/ so coding agents can read
the same framework rules as the repo maintainers. .kovo/endpoint-posture.json is generated by the
endpoint-posture test and checked by npm run check:endpoint-posture after the production build.
The graph workflow#
The starter does not write or commit a root graph.json. The app facts live in the authored
modules above; derive or construct the graph in tests when a product rule needs CI coverage:
pnpm --filter @kovojs/example-commerce run build:demo
pnpm --filter @kovojs/example-crm test -- src/interactive-app.test.tskovo explain output is stable and diffable by design. When a product rule matters, assert it in a
test or graph script and run it in CI. The kovo check & kovo explain guide
walks through the recipes.
Styling#
StyleX is the default component styling path. Author typed @kovojs/style objects in TSX, use
plain document CSS for fonts, page chrome, and theme tokens, and declare stylesheet hints for every
page, mutation fragment, and deferred stream so late HTML arrives styled.
Deployment shape#
A Kovo app deploys as a stateless server: mutation responses are ordinary HTML over the wire and
the server keeps no record of what's on screen. kovo build ./src/app.tsx emits dist/server, and
npm start runs dist/server/server.mjs for the Node preset.
The deployment guide covers the two real obligations: keeping versioned
/c/* client modules published across deploys, and not breaking the stateless-server guarantee.
Next#
- Quickstart - make the first schema/query/mutation change.
- Troubleshooting & upgrading - fix the common onboarding snags and keep a starter current.
Spec & diagnostics
Starter auth/session shape and delegated session providers: SPEC §6.6. Graph and explain audit surfaces: SPEC §11.4. Styling contract and stylesheet hints: SPEC §13.1. Deploy-time client module retention and stateless server rules: SPEC §9.5.