Menu

Getting Started

View as Markdown

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 scaffold

The 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:

tsx
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:

tsx
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 the demo@example.com credential with the random KOVO_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:

  1. Add the table and domain metadata in schema.ts.
  2. Seed or connect storage in db.ts.
  3. Add typed reads in queries.ts.
  4. Add guarded writes in mutations.ts.
  5. 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:

sh
pnpm --filter @kovojs/example-commerce run build:demo
pnpm --filter @kovojs/example-crm test -- src/interactive-app.test.ts

kovo 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#

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.