Todo application lab
Volume: 2: Application Engineering: Data & Web Source Directory:
examples/todo/Components:model.giz,pages/index.ghtml,main.gizTarget Port: Local HTTP (http://127.0.0.1:8080or custom port) Verification Status: Verified viatests/web/todo.test.gizand CDP browser journey.
1. Project Initialization & File Structure
Create a new project using the canonical todo template:
giz init my-todo-app --template todo
cd my-todo-appThe template generates three core files:
my-todo-app/
├── model.giz # Data schema definition & validation rules
├── pages/
│ └── index.ghtml # Declarative reactive user interface
└── main.giz # Application entry point & HTTP listener2. Step 1: Declaring the Model (model.giz)
Open model.giz. The entire persistence contract is expressed in 11 lines of typed Gizlang code:
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:
<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:
// 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:
giz run main.gizOpen 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:

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:

- 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. - Checking off an item in Tab B: Click the checkbox next to
"Review Gizlang Learning page". Tab A instantly displays the strikethrough styling andDonebadge. - Zero Configuration: The database, WebSocket server, static asset bundler, and reactive state synchronization run entirely inside the single native Gizlang binary.