Todos Example
The starter template comes with a todos application.

The server defines a few routes.
func main() { defer servers.Start(server) server.Efs = efs server.Routes = []routes.Route{ {Pattern: "GET /", Handler: fallback.View}, {Pattern: "GET /welcome", Handler: welcome.View}, {Pattern: "GET /todos", Handler: todos.View}, {Pattern: "POST /toggle", Handler: todos.Toggle}, {Pattern: "POST /add", Handler: todos.Add}, {Pattern: "POST /remove", Handler: todos.Remove}, }}Fallback
Section titled “Fallback”The GET / pattern acts as a fallback.
With that in mind, the fallback handler tries to send back a
matching file using send.RequestedFile() or, if it doesn’t exist, the "Welcome" view using welcome.View().
func View(client *clients.Client) { if !send.RequestedFile(client) { welcome.View(client) }}Welcome View
Section titled “Welcome View”The "Welcome" view, among other things, renders a hyperlink pointing to "GET /todos".
func View(client *clients.Client) { send.View(client, views.View{Name: "Welcome"})}<Layout title="Welcome"> <Logo /> {@render Description()} <div class="pt-6"></div> <div class="flex justify-center gap-2 relative"> {@render BackgroundEffect()} {@render TodosButton()} {@render DocumentationButton()} </div></Layout>{#snippet TodosButton()} <a class="btn btn-primary btn-lg" {...href("/todos")}> <span>Show Todos</span> <Icon path={mdiArrowRight} size="18" /> </a>{/snippet}Todos View
Section titled “Todos View”The "GET /todos" pattern is then captured by a Go handler function, which sends back
the "Todos" view along with a list of items retrieved from the user’s session.
func View(client *clients.Client) { session := sessions.NewDefault() receive.Session(client, &session) defer func() { session.Error = "" }() send.View(client, views.View{Name: "todos", Props: Props{ Error: session.Error, Items: session.Todos, }})}The "Todos" view is a CRUD web ui.
<script lang="ts"> //... import type { Props, Todo } from "$gen/types/main/lib/routes/todos/Props" let { todos = [], error }: Props = $props() //...</script><Layout title="Todos"> <div class="w-full min-w-[450px] max-w-2xl"> <div class="text-center"> {@render Description()} </div> <div class="card-body relative p-6"> {@render AddTodoForm()} <div class="divider"></div> {@render ShowTodosList(todos)} {@render BackButton()} </div> </div></Layout>List Todos
Section titled “List Todos”Items are listed by iterating over todos.
{#snippet ShowTodosList(todos: sessions.Todo[])} {#if todos.length > 0} {#each todos as todo, index (index)} <div in:slide out:slide class="flex w-full text-base-content/80"> {@render ToggleTodoButton(todo, index)} {@render RemoveTodoButton(index)} </div> {/each} {@render CountUncheckedTodos()} {:else} {@render NoTodosFound()} {/if}{/snippet}Each item has remove and toggle buttons.
Remove Todos
Section titled “Remove Todos”Items are removed by submitting a form to "POST /remove".
{#snippet RemoveTodoButton(index: number)} <form method="POST" {...action("/remove")}> <input type="hidden" name="index" value={index} /> <button type="submit" class="btn btn-ghost btn-sm btn-square hover:text-error hover:bg-error/20 transition-colors" aria-label="Delete" > <Icon path={mdiClose} size="18" /> </button> </form>{/snippet}The form is then captured by the Remove handler,
which does some basic validation, error handling
and then finally removes the item from the session.
type RemoveForm struct { Index int `form:"index"`}func Remove(client *clients.Client) { session := sessions.NewDefault() receive.Session(client, &session)
var form RemoveForm if !receive.Form(client, &form) { session.Error = "could not parse form" send.Navigate(client, "/todos") return }
if count := len(session.Todos); form.Index >= count || form.Index < 0 { session.Error = "index out of bounds" send.Navigate(client, "/todos") return }
session.Todos = append( session.Todos[:form.Index], session.Todos[form.Index+1:]..., )
send.Navigate(client, "/todos")}Toggle Todos
Section titled “Toggle Todos”Items are toggled by submitting a form to "POST /toggle".
{#snippet ToggleTodoButton(todo: Todo, index: number)} {@const aria = todo.checked ? "Uncheck" : "Check"} {@const value = todo.checked ? "0" : "1"} {@const icon = todo.checked ? mdiCheckCircleOutline : mdiCircleOutline} <form {...action("/toggle")} class="grow content-center"> <input type="hidden" name="index" value={index} /> <input type="hidden" name="value" {value} /> <button type="submit" class="w-full flex cursor-pointer" class:line-through={todo.checked} class:text-base-content={todo.checked} class:opacity-50={todo.checked} aria-label={aria} > <Icon path={icon} /> <div class="pr-4"></div> <span>{todo.description}</span> </button> </form>{/snippet}The form is then captured by the Toggle handler.
type ToggleForm struct { Index int `form:"index"` Value int `form:"value"`}func Toggle(client *clients.Client) { session := sessions.NewDefault() receive.Session(client, &session)
var form ToggleForm if !receive.Form(client, &form) { session.Error = "could not parse form" send.Navigate(client, "/todos") }
if count := len(session.Todos); form.Index >= count || form.Index < 0 { session.Error = "index out of bounds" send.Navigate(client, "/todos") return }
session.Todos[form.Index].Checked = form.Value > 0
send.Navigate(client, "/todos")}Add Todos
Section titled “Add Todos”Items are added by submitting a form to POST /add.
{#snippet AddTodoForm()} <form {...action("/add")} class="flex"> <input type="text" name="description" placeholder="Add a new task..." class="input bg-base-100/ text-lg w-full" /> <div class="pt-4"></div> <button type="submit" class="btn btn-ghost text-lg"> <Icon path={mdiPlus} size="20" /> <span>Add</span> </button> </form>
{#if error} <div class="pt-4"></div> <div in:slide out:slide class="alert alert-error"> <span>{error}</span> </div> {/if}{/snippet}The form is then captured by the Add handler.
type AddForm struct { Description string `form:"description"`}func Add(client *clients.Client) { session := sessions.NewDefault() receive.Session(client, &session)
var form AddForm if !receive.Form(client, &form) { session.Error = "could not parse form" send.Navigate(client, "/todos") return }
if form.Description == "" { session.Error = "description cannot be empty" send.Navigate(client, "/todos") return }
session.Todos = append(session.Todos, sessions.Todo{ Checked: false, Description: form.Description, })
send.Navigate(client, "/todos")}More Examples
Section titled “More Examples”| Description | Hyperlink |
|---|---|
| A live chat application | https://github.com/razshare/frizzante-example-chat |
| A blog application with login and registration forms | https://github.com/razshare/frizzante-example-blog |