Build Your First App
Extend the starter app with your own schema so you can see the schema-to-runtime workflow end to end.
After kizaki init, your app already has a working auth flow and a Todo entity. The best way to learn Kizaki is to change that schema and watch the rest of the stack follow.
This page follows the core Kizaki development loop on purpose:
- change the schema
- generate a migration
- add a server function
- call it from the app
If this loop feels clean, the rest of the platform will too.
Add A New Entity
Update schema/main.inspire:
auth {
providers: [email],
sessionDuration: 30d,
principal {
displayName: string @from(Profile.displayName),
}
}
entity Profile {
userId: __User.id @unique,
displayName: string @minLength(1) @maxLength(100),
bio: string?,
@grant read to *
@grant write where resource.userId == principal.id
}
entity Project {
name: string @minLength(1) @maxLength(120),
ownerId: __User.id,
@grant read, write, delete where resource.ownerId == principal.id
}This adds a real database entity and a policy model in the same edit.
That is the important shift to notice. In Kizaki, a schema change is not just a database change. It is also the beginning of the access model and the generated developer experience for that entity.
Create The Migration
Schema-affecting changes should become committed migrations:
kizaki migrate plan --name add_projects
kizaki migrate applyIf the planner needs help, it will write [RESOLVE] markers into the generated plan.inspire. Edit the plan, then run kizaki migrate apply again.
At this point, you have already done three meaningful things:
- changed the application model
- captured the schema change in committed history
- prepared the generated surfaces to understand the new entity
Add A Server Function
Create an exposed function in src/functions.ts:
import { Project } from "@kizaki/schema";
import { getPrincipal, insert, query } from "@kizaki/sdk";
/** @expose */
export async function createProject(name: string): Promise<Project> {
const me = getPrincipal();
const [project] = await query(
insert(Project)
.values({ name, ownerId: me.id })
.returning(),
);
return project;
}This is a good first example of the server-side style Kizaki wants:
- take business input
- read the current principal
- write through the generated schema object
- return a typed result
Call It From The App
import { createProject } from "@kizaki/client";
await createProject("Launch site");That call is the payoff for the compiler path. The frontend is not hand-authoring a network request. It is calling a generated function that stays aligned with your server code.
What To Notice
This single flow already demonstrates most of the Kizaki model:
- Inspire defines the durable system shape
- migrations keep schema changes reviewable
- exposed functions become generated client calls
- the browser stays focused on product behavior instead of API plumbing
What This Teaches
- Inspire defines your model
- committed migrations evolve your schema safely
/** @expose */turns server functions into generated client calls- authorization stays in the schema instead of leaking into UI code
Continue with Add Authentication.