The official Goddo demo (in src/ in the repository) is a full Todo API: CRUD with validation via t, automatic documentation with @goddo/openapi, an /llms.txt endpoint for AI agents, and a true HATEOAS / HTMX architecture with TSX Server-Side Rendering.
Standalone Demo: If you want to see an example using the published JSR packages, check out the goddo-example repository.
Entrypoint
src/index.ts is the file that actually starts the server: it imports the app instance defined in src/app.tsx and calls .listen() on it. Running deno task dev (or deno task start) executes this file:
import { app } from './app.tsx'
app.listen(3000)
API & Content Negotiation
The src/app.tsx file contains the API routes. Notice how Goddo easily handlesContent Negotiation: if the request comes from HTMX (checking the HX-Request header), it returns the rendered TSX component fragment. Otherwise, it functions as a standard JSON REST API.
import { Goddo, t } from '@goddo/core'
import { html } from '@goddo/html'
import { openapi } from '@goddo/openapi'
import { llmstxt } from '@goddo/llms-txt'
import { renderPage, type Todo } from './page.tsx'
import { TodoItem } from './components/TodoItem.tsx'
// Seed data — single source of truth for the initial store state
const SEED: [number, Todo][] = [
[1, { id: 1, title: 'Buy groceries', completed: true }],
[2, { id: 2, title: 'Walk the dog', completed: false }],
]
// In-memory store (Map = O(1) lookup/delete, analogous to a DB primary-key index)
const todos = new Map<number, Todo>(SEED)
let idCounter = SEED.length + 1
const todosArray = () => [...todos.values()]
/** Resets the store to its initial seed state. Used by benchmarks to avoid cross-benchmark pollution. */
export function resetStore() {
todos.clear()
for (const [k, v] of SEED) todos.set(k, { ...v })
idCounter = SEED.length + 1
}
export const app = new Goddo()
.use(
openapi({
documentation: {
info: { title: 'Todo API', version: '1.0.0', description: 'A simple Todo CRUD API.' },
},
}),
)
.use(llmstxt({
title: 'My Todos API',
description: 'An API to manage your tasks',
}))
.use(html())
.get('/', ({ redirect }) => redirect('/page'))
.get('/page', () => renderPage(todosArray()))
.group('/todos', (app) =>
app
.get('/', () => todosArray(), {
detail: { summary: 'List all todos', tags: ['Todos'] },
})
.get('/:id', ({ params: { id }, set }) => {
const todo = todos.get(id)
if (!todo) {
set.status = 404
return { error: 'Todo not found' }
}
return todo
}, {
params: t.Object({ id: t.Numeric() }),
detail: { summary: 'Get a todo by ID', tags: ['Todos'] },
})
.post('/', ({ body, headers }) => {
const todo: Todo = { id: idCounter++, title: body.title, completed: false }
todos.set(todo.id, todo)
if (headers['hx-request']) return TodoItem(todo)
return todo
}, {
body: t.Object({ title: t.String() }),
detail: { summary: 'Create a new todo', tags: ['Todos'] },
})
.patch('/:id', ({ params: { id }, body, headers, set }) => {
const todo = todos.get(id)
if (!todo) {
set.status = 404
return { error: 'Todo not found' }
}
if (body.title !== undefined) todo.title = body.title
if (body.completed !== undefined) todo.completed = body.completed
if (headers['hx-request']) return TodoItem(todo)
return todo
}, {
params: t.Object({ id: t.Numeric() }),
body: t.Object({
title: t.Optional(t.String()),
completed: t.Optional(t.Boolean()),
}),
detail: { summary: 'Partially update a todo', tags: ['Todos'] },
})
.delete('/:id', ({ params: { id }, headers, set }) => {
if (!todos.has(id)) {
set.status = 404
return { error: 'Todo not found' }
}
todos.delete(id)
if (headers['hx-request']) return ''
return { success: true }
}, {
params: t.Object({ id: t.Numeric() }),
detail: { summary: 'Delete a todo', tags: ['Todos'] },
}))
HATEOAS Interface (HTMX + Alpine.js)
The UI is split into components to enable hypermedia-driven interactions.src/page.tsx orchestrates the page:
import { Layout } from './components/Layout.tsx'
import { TodoItem } from './components/TodoItem.tsx'
export type Todo = { id: number; title: string; completed: boolean }
export function renderPage(todos: Todo[]) {
return (
<Layout>
<main x-data>
<article style='margin-top: 3rem;'>
<h1 style='display: flex; align-items: center; gap: 0.5rem;'>
<i class='ph ph-list-checks' style='color: var(--pico-primary);'></i> My Todos
</h1>
<form
x-data="{ title: '' }"
hx-post='/todos'
hx-ext='json-enc'
hx-target='#todo-list'
hx-swap='beforeend'
{...{
'x-on:htmx:after-request': 'if($event.detail.successful) title = ""',
}}
>
<fieldset role='group'>
<input
type='text'
name='title'
x-model='title'
placeholder='What needs to be done?'
required
/>
<button
type='submit'
style='display: flex; align-items: center; justify-content: center; gap: 0.25rem;'
>
<i class='ph ph-plus-circle'></i> Add
</button>
</fieldset>
</form>
<table>
<tbody id='todo-list'>
{todos.map((todo) => <TodoItem key={todo.id} {...todo} />)}
</tbody>
</table>
</article>
<article style='margin-top: 1rem; background-color: var(--pico-form-element-background); text-align: center; padding: 2rem;'>
<h3 style='margin-bottom: 0.5rem; display: flex; align-items: center; justify-content: center; gap: 0.5rem;'>
<i class='ph ph-book-open-text' style='color: var(--pico-primary);'></i>{' '}
API Documentation
</h3>
<p style='margin-bottom: 1.5rem; color: var(--pico-muted-color);'>
Want to see the API that powers this app? Check out the auto-generated interactive
documentation.
</p>
<a
href='/docs'
role='button'
class='secondary'
style='display: inline-flex; align-items: center; gap: 0.5rem;'
>
View API Docs <i class='ph ph-arrow-right'></i>
</a>
</article>
</main>
</Layout>
)
}
src/components/TodoItem.tsx is a standalone component that replaces itself in the DOM via hx-swap="outerHTML" when toggled, edited, or deleted:
import type { Todo } from '../page.tsx'
export function TodoItem(todo: Todo & { key?: string | number }) {
// We use hx-swap="outerHTML" to replace the entire <tr> element with the new one
// hx-ext="json-enc" is used to keep sending JSON to match the strict type schemas
return (
<tr id={`todo-${todo.id}`}>
<td width='100%'>
<label>
<input
type='checkbox'
checked={todo.completed}
hx-patch={`/todos/${todo.id}`}
hx-ext='json-enc'
hx-vals={JSON.stringify({ completed: !todo.completed })}
hx-target={`#todo-${todo.id}`}
hx-swap='outerHTML'
/>
{todo.completed ? <s>{todo.title}</s> : todo.title}
</label>
</td>
<td align='right' style='vertical-align: middle;'>
<div style='display: flex; gap: 0.5rem; justify-content: flex-end;'>
<button
type='button'
x-data={`{ currentTitle: '${todo.title.replace(/'/g, "\\'")}' }`}
hx-patch={`/todos/${todo.id}`}
hx-ext='json-enc'
hx-target={`#todo-${todo.id}`}
hx-swap='outerHTML'
{...{
'x-on:htmx:config-request':
"let newTitle = prompt('Edit todo:', currentTitle); if (newTitle !== null && newTitle.trim() !== '') { $event.detail.parameters.title = newTitle.trim(); } else { $event.preventDefault(); }",
}}
style='margin-bottom: 0; min-width: 90px; display: flex; align-items: center; justify-content: center; gap: 0.25rem; background-color: var(--pico-secondary-background); border-color: var(--pico-secondary-border);'
>
<i class='ph ph-pencil-simple'></i> Edit
</button>
<button
type='button'
hx-delete={`/todos/${todo.id}`}
hx-target={`#todo-${todo.id}`}
hx-swap='outerHTML'
style='margin-bottom: 0; min-width: 90px; display: flex; align-items: center; justify-content: center; gap: 0.25rem;'
>
<i class='ph ph-trash'></i> Delete
</button>
</div>
</td>
</tr>
)
}
src/components/Layout.tsx encapsulates the document boilerplate:
git clone https://github.com/carlosxfelipe/goddo.git
cd goddo
deno task dev
The demo runs at http://localhost:3000. The UI is at /page, the OpenAPI documentation at /docs and the endpoint for LLMs at /llms.txt.
Bruno & Benchmarks
The repository also includes API collections for Bruno (in the bruno/ folder) and a set of performance benchmarks (deno task bench) covering routing, compiled handler throughput, and compilation overhead.