Todos Example
The starter template comes with a todos application.



Main
The server defines a few routes.
func main() {
  server := servers.New()
  server.Efs = efs
  server.Render = ssr.New(1)
  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},
  }
  if err := servers.Start(server); err != nil {
    log.Fatal(err)
  }
}


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 is sent instead using welcome.View() .
func View(client *clients.Client) {
  if !send.RequestedFile(client) {
    welcome.View(client)
  }
}


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"})
}
<script lang="ts">
  import Icon from "$lib/components/icons/icon.svelte"
  import Layout from "$lib/components/layout.svelte"
  import Logo from "$lib/components/logo.svelte"
  import { href } from "$lib/scripts/core/href.ts"
  import { mdiArrowRight } from "@mdi/js"
</script>

<Layout title="Welcome">
  <Logo />
  {@render Description()}
  <div class="pt-6"></div>
  <div class="flex justify-center gap-2 relative">
    {@render TodosButton()}
    {@render DocumentationButton()}
  </div>
</Layout>

{#snippet Description()}
  <p>Modern Go + Svelte Framework</p>
{/snippet}

{#snippet TodosButton()}
  <a {...href("/todos")}>
    <button>
      <span>Show Todos</span>
      <Icon path={mdiArrowRight} />
    </button>
  </a>
{/snippet}

{#snippet DocumentationButton()}
  <a href="https://razshare.github.io/frizzante-docs/guides/get-started" target="_blank">
    <button>Documentation</button>
  </a>
{/snippet}


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) {
  var session schema.Session
  defer func() {
    if err := databases.Queries.ModifySessionById(client.Request.Context(), schema.ModifySessionByIdParams{
      ID: session.ID,
    }); err != nil {
      logs.Error(client, err)
    }
  }()
  receive.Session(client, &session)
  context := client.Request.Context()
  var err error
  var todos []schema.Todo
  if todos, err = databases.Queries.FindTodosBySessionId(context, session.ID); err != nil {
    session.Error = err.Error()
    return
  }
  send.View(client, views.View{Name: "Todos", Props: Props{
    Error: session.Error,
    Items: todos,
  }})
}
The Todos view is a CRUD web ui.
<script lang="ts">
    //...
    import type { Props, schemas } from "$lib/types/server/main/lib/routes/todos/props"
    let { items, error }: Props = $props()
    //...
</script>
<Layout title="Todos">
  {@render Description()}
  {@render AddTodoForm()}
  {@render TodoList(items)}
  {@render BackButton()}
</Layout>


List Todos
Items are listed by iterating over the items prop (which comes from the server).
{#snippet TodoList(items: schema.Todo[])}
  {#if items.length > 0}
    <div class="todo-list">
      {#each items as todo, index (index)}
        <div transition:slide class="item">
          {@render ToggleTodoButton(todo, index)}
          {@render RemoveTodoButton(index)}
        </div>
      {/each}
    </div>
    {@render CountUncheckedTodos()}
  {:else}
    <div class="todo-list">
      {@render NoTodosFound()}
    </div>
  {/if}
{/snippet}
Each item has remove and toggle buttons.
Note
Type sessions.Todo is an autogenerated type definition.
See type definitions.


Remove Todos
Items are removed by submitting a form to POST /remove .
{#snippet RemoveTodoButton(id: string)}
  <form method="POST" {...action("/remove")}>
    <input type="hidden" name="id" value={id} />
    <label aria-label="Delete">
      <Icon path={mdiClose} />
      <button aria-label="remove"></button>
    </label>
  </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.
func Remove(client *clients.Client) {
  var session schema.Session
  defer send.Navigate(client, "/todos")
  defer func() {
    if err := databases.Queries.ModifySessionById(client.Request.Context(), schema.ModifySessionByIdParams{
      ID:    session.ID,
      Error: session.Error,
    }); err != nil {
      logs.Error(client, err)
    }
  }()
  receive.Session(client, &session)
  var form struct {
    Id string `form:"id"`
  }
  if !receive.Form(client, &form) {
    session.Error = "could not parse form"
    return
  }
  context := client.Request.Context()
  if err := databases.Queries.RemoveTodosByIdAndSessionId(context, schema.RemoveTodosByIdAndSessionIdParams{
    ID:        form.Id,
    SessionID: session.ID,
  }); err != nil {
    session.Error = err.Error()
    return
  }
}


Toggle Todos
Items are toggled by submitting a form to POST /toggle .
{#snippet ToggleTodoButton(todo: schema.Todo, id: string)}
  {@const aria = todo.checked > 0 ? "Uncheck" : "Check"}
  {@const nextValue = todo.checked > 0 ? 0 : 1}
  {@const icon = todo.checked ? mdiCheckCircleOutline : mdiCircleOutline}
  <form method="POST" {...action("/toggle")}>
      <input type="hidden" name="id" value={id} />
      <input type="hidden" name="value" value={nextValue} />
      <label aria-label={aria}>
        <Icon path={icon} />
        {#if todo.checked}
          <strike>
            <span>{todo.description}</span>
          </strike>
        {:else}
          <span>{todo.description}</span>
        {/if}
        <button aria-label="toggle"></button>
      </label>
  </form>
{/snippet}
The form is then captured by the Toggle handler.
func Toggle(client *clients.Client) {
  var session schema.Session
  defer send.Navigate(client, "/todos")
  defer func() {
    if err := databases.Queries.ModifySessionById(client.Request.Context(), schema.ModifySessionByIdParams{
      ID:    session.ID,
      Error: session.Error,
    }); err != nil {
      logs.Error(client, err)
    }
  }()
  receive.Session(client, &session)
  var form struct {
    Id    string `form:"id"`
    Value int64  `form:"value"`
  }
  if !receive.Form(client, &form) {
    session.Error = "could not parse form"
    return
  }
  if err := databases.Queries.ToggleTodosByIdAndSessionId(client.Request.Context(), schema.ToggleTodosByIdAndSessionIdParams{
    ID:        form.Id,
    SessionID: session.ID,
    Checked:   form.Value,
  }); err != nil {
    session.Error = err.Error()
    return
  }
}


Add Todos
Items are added by submitting a form to POST /add .
{#snippet AddTodoForm()}
  <form method="POST" {...action("/add")}>
    <input type="text" name="description" placeholder="Add a new task..." />
    <br />
    <button type="submit">
      <Icon path={mdiPlus} />
      <span>Add</span>
    </button>
  </form>
  {#if error}
    <br />
    <div transition:slide>
      <span>{error}</span>
    </div>
  {/if}
{/snippet}
The form is then captured by the Add handler.
func Add(client *clients.Client) {
  var session schema.Session
  defer send.Navigate(client, "/todos")
  defer func() {
    if err := databases.Queries.ModifySessionById(client.Request.Context(), schema.ModifySessionByIdParams{
      ID:    session.ID,
      Error: session.Error,
    }); err != nil {
      logs.Error(client, err)
    }
  }()
  receive.Session(client, &session)
  var form struct {
 	  Description string `form:"description"`
  }
  if !receive.Form(client, &form) {
    session.Error = "could not parse form"
    return
  }
  if form.Description == "" {
    session.Error = "description cannot be empty"
    return
  }
  ido, err := uuid.NewV4()
  if err != nil {
    session.Error = err.Error()
    return
  }
  id := ido.String()
  context := client.Request.Context()
  if err = databases.Queries.AddTodoWithIdAndSessionId(context, schema.AddTodoWithIdAndSessionIdParams{
    ID:          id,
    SessionID:   session.ID,
    Description: form.Description,
  }); err != nil {
    session.Error = err.Error()
    return
  }
}