https://blog.yusu.ke/hono-htmx-cloudflare/
Blog - Yusuke Wada
About Twitter GitHub Screenshot
Hono + htmx + Cloudflare is a new stack
Jul 21, 2023
---------------------------------------------------------------------
Hono + htmx + Cloudflare is a new stack
As a former backend engineer, I sometimes find React complex.
Moreover, as a framework developer, creating a hydration mechanism
can be troublesome. But we often end up using React.
One of the main advantages of using React is JSX. At first, JSX seems
strange - "Why are HTML tags in JavaScript!!!" However, once I get
used to it, I find that JSX is flexible and comfortable to use.
Today, I'll introduce a tech stack where the main point is using JSX
purely as a server-side template engine. This means we can use JSX
without React.
Hono JSX Middleware
Hono - a JavaScript framework for edges - includes JSX middleware.
You can write HTML with JSX but it's strictly for server-side
rendering, not for the client. As such, it serves as a template
engine, much like Handlebars, EJS, mustache, or others.
const app = new Hono()
app.get('/', (c) => {
return c.html(
Hello!
)
})
A Hono app can run on edge servers like Cloudflare Workers, Fastly
Compute@Edge, or Deno Deploy. This allows for incredibly fast
server-side rendering. Moreover, it doesn't perform "hydration" for
JavaScript, meaning you don't lose user experience without the need
for SPA transition. This combination of edge-based SSR and the
absence of hydration makes for a very speedy setup.
htmx
htmx is a library that enables Ajax without the need to write
JavaScript.
It's comparable to Hotwire, which is used by Ruby on Rails. However,
unlike using React with a REST API, htmx can easily integrate with
server-side JSX, making it simpler to create interactive experiences.
The stack
The entire stack includes the following components:
* Hono + JSX Middleware
* htmx
* Zod
* Tailwind CSS
* Cloudflare Workers
* Cloudflare D1
Cloudflare D1 is a database service that runs SQLite on Cloudflare
edges. Although it's currently in "alpha" status and not recommended
for production use, it's already fast and perfectly suitable for
Proof of Concept (PoC) projects.
In the example below, I use Zod to validate incoming values. Hono's
Zod Validator Middleware is incredibly useful as it integrates with
Hono, allowing us to easily get the types of validated values.
Screenshot
html.js
I must express my gratitude. This idea is based on @dctanner's tweet.
He named it the "html.js" stack. You can find it in this repository:
https://github.com/dctanner/htmljs-todo-example
100 lines Todo app
It's amazing. I was able to create a real Todo App example that
inserts and deletes data in D1 SQLite on the edge with just 100 lines
of code. It's fast (~100ms) and lightweight (gzipped worker size: 22
KB)!
Here is the demo:
output
Build size:
Screenshot
Code
Normally, when I have to show an example code, I have to choose
specific parts of the code and paste a few lines. However, this
example is just 100 lines, so I'll show the entire code.
comonent.tsx:
import { html } from 'hono/html'
export const Layout = (props: { children: any }) => html`
Hono + htmx
)
index.tsx:
import { Hono } from 'hono/quick'
import { z } from 'zod'
import { zValidator } from '@hono/zod-validator'
import { Layout, AddTodo, Item } from './components'
type Bindings = {
DB: D1Database
}
type Todo = {
title: string
id: string
}
const app = new Hono<{ Bindings: Bindings }>()
app.get('/', async (c) => {
const { results } = await c.env.DB.prepare(`SELECT id, title FROM todo;`).all()
const todos = results as unknown as Todo[] // Currently, should fix a type mismatch.
return c.html(
{todos.map((todo) => {
return
})}
)
})
app.post(
'/todo',
zValidator(
'form',
z.object({
title: z.string().min(1)
})
),
async (c) => {
const { title } = c.req.valid('form')
const id = crypto.randomUUID()
await c.env.DB.prepare(`INSERT INTO todo(id, title) VALUES(?, ?);`).bind(id, title).run()
return c.html()
}
)
app.delete('/todo/:id', async (c) => {
const id = c.req.param('id')
await c.env.DB.prepare(`DELETE FROM todo WHERE id = ?;`).bind(id).run()
c.status(200)
return c.body(null)
})
export default app
Isn't it elegant?
You can find the entire project here:
https://github.com/yusukebe/hono-htmx
Am I talking about PHP?
Perhaps, you're wondering:
Are you talking about PHP?
To which I'll answer:
No. But it's quite similar!
It really feels like PHP, or perhaps Ruby on Rails. But I think PHP
is not bad. Moreover, this stack has several advantages for me:
* It runs on the edges.
* I can use JavaScript/JSX.
* I can avoid spaghetti code by organizing the code well.
As I mentioned at the beginning, I used to be a backend engineer, so
this approach to website creation is more familiar and comfortable
for me. It's simple and clean.
Going forward
There are a few things we need to work on to stabilize this stack.
One is enabling file-based routing. Also, I'm not sure if using
Hono's JSX middleware is the best approach, maybe Preact would be a
better choice.
Anyway, this stack has a nostalgic yet new feeling to it. Oh, I've
forgotten one thing we need to do. We should name this stack!
Thanks
Again, thank you to @dctanner for the inspiring idea. I also
recommend checking out his repository:
https://github.com/dctanner/htmljs-todo-example
(c) 2023 Yusuke Wada. All rights reserved.