Kizaki
Learn

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:

  1. change the schema
  2. generate a migration
  3. add a server function
  4. 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 apply

If 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.

On this page