GizStack
View .mdOpen in ChatGPTOpen in Claude

Todo application lab

Volume: 2: Application Engineering: Data & Web Source Directory: examples/todo/ Components: model.giz, pages/index.ghtml, main.giz Target Port: Local HTTP (http://127.0.0.1:8080 or custom port) Verification Status: Verified via tests/web/todo.test.giz and CDP browser journey.


1. Project Initialization & File Structure

Create a new project using the canonical todo template:

sh
giz init my-todo-app --template todo
cd my-todo-app

The template generates three core files:

text
my-todo-app/
├── model.giz          # Data schema definition & validation rules
├── pages/
│   └── index.ghtml    # Declarative reactive user interface
└── main.giz           # Application entry point & HTTP listener

2. Step 1: Declaring the Model (model.giz)

Open model.giz. The entire persistence contract is expressed in 11 lines of typed Gizlang code:

typescript
import * as Model from "gizdb:model";

/** A thing to do. The key is made when the record is added and sorts by time. */
@Model.Table("todos")
export type Todo = {
    @Model.Key
    id: string;
    @Model.Min(1) @Model.Max(80)
    text: string;
    done: boolean;
};

Key Points:

  • @Model.Table("todos"): Registers the table in the embedded GizDB store.
  • @Model.Key: Auto-generates a monotonic, time-sorted unique string identifier upon insertion.
  • @Model.Min(1) @Model.Max(80): Enforces input length bounds automatically on both form submission and API mutations.

3. Step 2: Constructing the Reactive View (pages/index.ghtml)

In pages/index.ghtml, we bind the table to the reactive UI:

html
<script>
import {Field, Check, Chip, controlId, describedBy} from "gizweb";
import {Todo} from "../model.giz";

let filter = "all" @url;
const todos = table<Todo>("id desc");
</script>

<title>Todos ({todos.count(t => !t.done)} open)</title>
<h1>Todos ({todos.count(t => !t.done)} open)</h1>

<form create={todos} class="ui-form">
  <Field name="text" label="New todo" hint="">
    <input id={controlId("text")} name="text" placeholder="What next?" aria-describedby={describedBy("text", false)}/>
  </Field>
  <button class="ui-button pri">Add</button>
</form>

<Field name="filter" label="Show" hint="">
  <select id={controlId("filter")} value={filter} aria-describedby={describedBy("filter", false)}>
    <option value="all">All</option>
    <option value="open">Open</option>
    <option value="done">Done</option>
  </select>
</Field>

<ul class="todos">
  <li each={t of todos.where(t => filter === "all" || t.done === (filter === "done"))} key={t.id} class:done={t.done}>
    <Check label={t.text}><input type="checkbox" checked={t.done}/></Check>
    <Chip if={t.done} label="Done" tone="ok"/>
    <button class="ui-button ghost" onclick={todos.remove(t)}>Remove</button>
  </li>
</ul>

<button class="ui-button" onclick={todos.removeWhere(t => t.done)}>Clear done</button>
<p if={todos.length === 0} class="ui-muted">Nothing to do.</p>

<style>
  .todos { list-style: none; padding: 0; }
  .todos li { display: flex; align-items: center; gap: 8px; }
  .done .ui-check span { text-decoration: line-through; }
</style>

4. Step 3: Application Entry Point (main.giz)

In main.giz, configure the HTTP listener:

typescript
// A live todo app over a typed table: the page reads and writes Todo records, the form checks the model's rules, and
// every open tab shows a change by itself. Run: giz run main.giz, then open http://127.0.0.1:8080. With
// TLS_DOMAINS=todo.example.com set, it gets its certificate and serves HTTPS with HTTP/2 and HTTP/3.
// The todos live in the GizDB database that this process serves, in the folder data/db: there is no server to start.
// DATABASE_URL may name a gizdb or PostgreSQL server instead.
import {System} from "gizlang:system";
import * as Model from "gizdb:model";
import * as Pages from "gizweb:pages";
import {Todo} from "./model.giz";
import {site} from "./pages/.routes";

function main(sys: System): i64 {
    return Pages.app(sys, site, [Model.shape<Todo>()]).listen(8080);
}

5. Step 4: Running and Verifying Live Multi-Tab Sync

Run the application:

sh
giz run main.giz

Open two separate browser tabs at http://127.0.0.1:8080:

Live Initial View

When first loaded, the embedded GizDB store provisions the table schema automatically:

Todo App Initial State

Active Multi-Client Collaborative State

When concurrent clients interact with the application—adding tasks, updating filter views, and marking items complete—the embedded change stream keeps all views in synchronization:

Todo App Collaborative State

  1. Adding an item in Tab A: Type "Review Gizlang Learning page" and submit. Tab B immediately renders the new row and updates the open count without any manual page refresh.
  2. Checking off an item in Tab B: Click the checkbox next to "Review Gizlang Learning page". Tab A instantly displays the strikethrough styling and Done badge.
  3. Zero Configuration: The database, WebSocket server, static asset bundler, and reactive state synchronization run entirely inside the single native Gizlang binary.