Index: pages/todo/+Page.tsx
===================================================================
--- pages/todo/+Page.tsx	(revision 8ee45534cd667e6cced2ed96968c95d8de3b1029)
+++ pages/todo/+Page.tsx	(revision 8ee45534cd667e6cced2ed96968c95d8de3b1029)
@@ -0,0 +1,10 @@
+import { TodoList } from "./TodoList.js";
+
+export default function Page() {
+  return (
+    <>
+      <h1>To-do List</h1>
+      <TodoList />
+    </>
+  );
+}
Index: pages/todo/+config.ts
===================================================================
--- pages/todo/+config.ts	(revision 8ee45534cd667e6cced2ed96968c95d8de3b1029)
+++ pages/todo/+config.ts	(revision 8ee45534cd667e6cced2ed96968c95d8de3b1029)
@@ -0,0 +1,3 @@
+export const config = {
+  prerender: false,
+};
Index: pages/todo/+data.ts
===================================================================
--- pages/todo/+data.ts	(revision 8ee45534cd667e6cced2ed96968c95d8de3b1029)
+++ pages/todo/+data.ts	(revision 8ee45534cd667e6cced2ed96968c95d8de3b1029)
@@ -0,0 +1,12 @@
+// https://vike.dev/data
+
+import * as drizzleQueries from "../../database/drizzle/queries/todos";
+import type { PageContextServer } from "vike/types";
+
+export type Data = Awaited<ReturnType<typeof data>>;
+
+export async function data(_pageContext: PageContextServer) {
+  const todoItemsInitial = await drizzleQueries.getAllTodos(_pageContext.db);
+
+  return { todoItemsInitial };
+}
Index: pages/todo/TodoList.telefunc.ts
===================================================================
--- pages/todo/TodoList.telefunc.ts	(revision 8ee45534cd667e6cced2ed96968c95d8de3b1029)
+++ pages/todo/TodoList.telefunc.ts	(revision 8ee45534cd667e6cced2ed96968c95d8de3b1029)
@@ -0,0 +1,9 @@
+// We use Telefunc (https://telefunc.com) for data mutations.
+
+import * as drizzleQueries from "../../database/drizzle/queries/todos";
+import { getContext } from "telefunc";
+
+export async function onNewTodo({ text }: { text: string }) {
+  const context = getContext();
+  await drizzleQueries.insertTodo(context.db, text);
+}
Index: pages/todo/TodoList.tsx
===================================================================
--- pages/todo/TodoList.tsx	(revision 8ee45534cd667e6cced2ed96968c95d8de3b1029)
+++ pages/todo/TodoList.tsx	(revision 8ee45534cd667e6cced2ed96968c95d8de3b1029)
@@ -0,0 +1,36 @@
+import type { Data } from "./+data";
+import { onNewTodo } from "./TodoList.telefunc";
+import { useState } from "react";
+import { useData } from "vike-react/useData";
+
+export function TodoList() {
+  const { todoItemsInitial } = useData<Data>();
+  const [todoItems, setTodoItems] = useState<{ text: string }[]>(todoItemsInitial);
+  const [newTodo, setNewTodo] = useState("");
+  return (
+    <>
+      <ul>
+        {todoItems.map((todoItem, index) => (
+          // biome-ignore lint: example
+          <li key={index}>{todoItem.text}</li>
+        ))}
+      </ul>
+      <div>
+        <form
+          onSubmit={async (ev) => {
+            ev.preventDefault();
+
+            const text = newTodo;
+            setTodoItems((prev) => [...prev, { text }]);
+            setNewTodo("");
+
+            await onNewTodo({ text });
+          }}
+        >
+          <input type="text" onChange={(ev) => setNewTodo(ev.target.value)} value={newTodo} />
+          <button type="submit">Add to-do</button>
+        </form>
+      </div>
+    </>
+  );
+}
