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 walks through the core development loop:

  1. change the schema
  2. generate a migration
  3. add a server function
  4. call it from the app

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 database entity and a policy model in the same edit.

A schema change in Kizaki is simultaneously a database change, an access control change, and a generated API change. The @grant rules take effect as soon as the migration applies.

The auth {} block here stays intentionally minimal so the first app can focus on the schema-to-runtime workflow. For the complete auth model, including password policy, account linking, and how /__auth/* is exposed, see Inspire Auth and Authentication.

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.migration. Edit the plan, then run kizaki migrate apply again.

At this point you have:

  • 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 or another file under src/:

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 shows the standard server-side pattern:

  • 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");

The frontend calls a generated function that stays aligned with your server code. No hand-authored network layer sits between them.

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