Skip to main content
Scaffold a full-stack task manager with a database, API, and UI — all typed end-to-end.
Want a minimal starting point instead? Use --template hello-world for a UI-only counter app:
bunx @vertz/create-vertz-app@latest my-app --template hello-world

Create a new project

1

Scaffold the app

bunx @vertz/create-vertz-app@latest my-app
cd my-app
bun install
This creates a complete project with a SQLite database, a REST API, and a styled UI with a shadcn-inspired theme — not empty boilerplate.
2

Start the dev server

bun run dev
Open http://localhost:3000. You should see a working task manager where you can create and complete tasks.

What you just got

The scaffolded project is a full-stack app:
my-app/
  src/
    api/
      env.ts               # Validated environment variables
      schema.ts            # Table + model definition
      db.ts                # SQLite with auto-migrations
      server.ts            # API server
      entities/
        tasks.entity.ts    # CRUD + access control
    pages/
      home.tsx             # Task list with query + form
    styles/
      theme.ts             # shadcn-inspired theme
    app.tsx                # App shell with SSR exports
    client.ts              # Typed API client
    entry-client.ts        # Client-side mount + HMR
  .env                     # Environment variables
  vertz.config.ts
  tsconfig.json
  package.json

Walk through the code

Schema — the source of truth

Open src/api/schema.ts. This defines the tasks table and model:
import { d } from 'vertz/db';

export const tasksTable = d.table('tasks', {
  id: d.uuid().primary({ generate: 'uuid' }),
  title: d.text(),
  completed: d.boolean().default(false),
  createdAt: d.timestamp().default('now').readOnly(),
  updatedAt: d.timestamp().default('now'),
});

export const tasksModel = d.model(tasksTable);
Every type in your API, client, and UI traces back to this definition. Change a column here and tsc shows errors everywhere that column is used.

Entity — schema to API endpoints

Open src/api/entities/tasks.entity.ts:
import { entity } from 'vertz/server';
import { tasksModel } from '../schema';

export const tasks = entity('tasks', {
  model: tasksModel,
  access: { list: () => true, get: () => true, create: () => true },
});
This single declaration generates GET /api/tasks, GET /api/tasks/:id, POST /api/tasks, and more — with access control required for every operation.

UI — reactive task list

Open src/pages/home.tsx. This is where query() and form() connect the UI to your API:
import { query, form, css } from 'vertz/ui';
import { api } from '../client';

export function HomePage() {
  const tasks = query(api.tasks.list());

  return (
    <div>
      {tasks.data?.items.map((task) => (
        <div key={task.id}>
          <span>{task.title}</span>
          <span>{task.completed ? 'Done' : 'Todo'}</span>
        </div>
      ))}
    </div>
  );
}
query() fetches data reactively — when the data loads, only the list updates. The api client is fully typed from your schema, so task.title is a string and task.completed is a boolean without any manual type definitions.

Try it

With the dev server running:
  1. Add a task — use the form in the UI to create a new task
  2. Check it off — mark a task as completed
  3. Edit the schema — try renaming title to name in tasksTable and watch tsc flag every place that references the old column — entity, client, and UI

Available commands

bun run dev        # Start dev server (API + UI + SSR + HMR)
bun run build      # Build for production
bun run start      # Start production server (after build)
bun run codegen    # Regenerate typed API client

Next steps

Installation

Add Vertz to an existing project manually.

Components

Props, children, and component patterns.

Reactivity

Deep dive into signals, computed, and effects.

Routing

Set up pages with type-safe routing.