Ringkasan Utama
Hono adalah framework web tanpa dependensi yang dibangun di atas Web Standards. Anda menulis satu API menggunakan objek Request dan Response yang sudah disediakan platform, lalu menjalankannya tanpa perubahan di Cloudflare Workers, Bun, Node, dan Deno hanya dengan menukar adapter tipis. Klien RPC bertipenya memberi frontend keamanan tipe penuh tanpa codegen.
Saya sudah cukup sering merilis layanan Express untuk tahu pajak yang diam-diam dibebankannya: ia terikat pada modul http Node, jadi ketika suatu hari Anda ingin menjalankan handler yang sama di runtime edge, Anda harus menulis ulang. Hono mengambil taruhan sebaliknya. Ia dibangun di atas objek Request dan Response Web Standard — primitif yang sama yang sudah Anda pakai di browser dan di Service Worker — sehingga framework itu sendiri tidak membawa asumsi runtime apa pun. Per Juli 2026 versinya v4.12, tanpa dependensi, dan preset hono/tiny beratnya di bawah 12kB.
Manfaat praktisnya adalah portabilitas. Saya bisa membuat prototipe API di Bun secara lokal untuk startup cepat, men-deploy-nya ke Cloudflare Workers untuk baca latensi rendah global, dan tetap menyimpan build Node untuk VPS lama — semuanya dari pohon sumber yang sama. Artikel ini membahas routing, middleware, adapter runtime, dan klien RPC bertipe, dengan trade-off yang benar-benar saya temui.
Routing: cepat karena matcher-nya diprakompilasi
RegExpRouter bawaan Hono mengompilasi setiap rute yang Anda daftarkan menjadi satu ekspresi reguler besar. Karena itu, mencocokkan path yang masuk mendekati waktu konstan berapa pun jumlah rutenya, bukan menelusuri radix tree per permintaan. Dalam benchmark yang dipublikasikan, ini membuat pencarian rute jauh lebih cepat daripada tree find-my-way yang menjalankan Fastify. API routing-nya sendiri akan terasa familier — handler metode, parameter path, dan wildcard — tetapi bentuk objek yang Anda sentuh murni Web Standard.
import { Hono } from 'hono'
const app = new Hono()
app.get('/health', (c) => c.json({ ok: true }))
app.get('/users/:id', (c) => {
const id = c.req.param('id') // typed path param
const verbose = c.req.query('verbose') // query string
return c.json({ id, verbose })
})
app.post('/users', async (c) => {
const body = await c.req.json() // Web Standard Request under the hood
return c.json({ created: body }, 201)
})
export default appMiddleware: satu tanda tangan, terkomposisi rapi
Middleware di Hono adalah fungsi yang menerima konteks dan callback next. Anda melakukan pekerjaan sebelum memanggil next, menunggunya, lalu melakukan pekerjaan sesudahnya — model onion klasik, tetapi setiap bagian portabel karena tak satu pun menjangkau global Node. Paket resminya mencakup daftar yang membosankan-tapi-esensial: CORS, verifikasi JWT, bearer auth, logging permintaan, kompresi, ETag, dan rate limiting. Anda memasangnya per path atau secara global.
import { Hono } from 'hono'
import { logger } from 'hono/logger'
import { cors } from 'hono/cors'
import { jwt } from 'hono/jwt'
const app = new Hono()
app.use('*', logger())
app.use('/api/*', cors({ origin: 'https://www.matthewswong.com' }))
app.use('/api/*', jwt({ secret: process.env.JWT_SECRET! }))
// custom middleware — same signature
app.use('*', async (c, next) => {
const start = Date.now()
await next()
c.header('X-Response-Time', `${Date.now() - start}ms`)
})Urutan penting dan short-circuit bersifat eksplisit: jika sebuah middleware mengembalikan Response alih-alih memanggil next, rantainya berhenti di situ. Begitulah guard auth menolak permintaan dengan rapi — kembalikan 401 dan jangan pernah menyentuh handler.
Aplikasi yang sama di Workers, Bun, dan Node
Bagian inilah yang meyakinkan saya. Aplikasi Hono Anda hanyalah objek dengan metode fetch yang menerima Request dan mengembalikan Response. Setiap runtime punya cara sendiri memasukkan permintaan, jadi Hono menyediakan adapter tipis per platform — kode aplikasi tidak pernah berubah, hanya file entry-nya. Cloudflare Workers dan Bun sama-sama berbicara fetch secara native, jadi Anda mengekspor aplikasi secara langsung. Node butuh jembatan dari modul http-nya, yang disediakan oleh adapter Node.
// app.ts — shared by ALL runtimes, never changes
import { Hono } from 'hono'
export const app = new Hono().get('/', (c) => c.text('Hello from anywhere'))
// --- Cloudflare Workers: worker.ts
import { app } from './app'
export default app // Workers calls app.fetch
// --- Bun: bun.ts -> bun run bun.ts
import { app } from './app'
export default { fetch: app.fetch, port: 3000 }
// --- Node: node.ts -> node node.ts
import { serve } from '@hono/node-server'
import { app } from './app'
serve({ fetch: app.fetch, port: 3000 })Portabilitas berakhir di titik API platform dimulai. Jika sebuah handler menjangkau binding KV Cloudflare, API file khusus Bun, atau stream Node, rute itu berhenti menjadi portabel. Simpan akses spesifik-runtime di balik antarmuka kecil yang bisa Anda tukar, atau Anda akan kehilangan properti yang justru menjadi alasan Anda memilih Hono.
RPC bertipe: keamanan tipe tanpa codegen
Fitur RPC Hono membagikan tipe rute Anda langsung ke frontend. Tidak ada file OpenAPI dan tidak ada langkah pembuatan kode — Anda mengekspor satu tipe TypeScript dari server dan klien hc membacanya. Jebakannya, dan ini footgun paling umum, adalah inferensi hanya bertahan jika Anda merangkai handler Anda menjadi satu ekspresi. Pecah rantai menjadi pernyataan terpisah dan klien kehilangan tipenya. Saya memakai validator permintaan agar input dan output yang diinferensikan benar-benar ditegakkan saat runtime, bukan hanya di editor.
// server.ts
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
const routes = new Hono()
.post(
'/posts',
zValidator('json', z.object({ title: z.string() })),
(c) => {
const { title } = c.req.valid('json')
return c.json({ id: 1, title }, 201)
},
) // <-- keep the chain unbroken so types are inferred
export type AppType = typeof routes
export default routes
// client.ts — frontend, fully typed, no codegen
import { hc } from 'hono/client'
import type { AppType } from './server'
const client = hc<AppType>('https://api.example.com')
const res = await client.posts.$post({ json: { title: 'Hello' } })
const data = await res.json() // data.title is typed as stringUntuk API besar, kompilasi tipe klien lebih awal alih-alih membiarkan editor menghitungnya ulang di setiap ketukan tombol. Dokumentasinya menyarankan membungkus hc dalam helper yang tipe kembaliannya dipatok dengan ReturnType, sehingga tsc melakukan instansiasi tipe berat sekali saat build dan IDE Anda tetap cepat.
Di mana Hono cocok — dan di mana tidak
Saya memilih Hono ketika beban kerjanya berbentuk API dan saya peduli untuk men-deploy ke lebih dari satu tempat: fungsi edge, layanan Bun, kontainer Node kecil. Ia sengaja tidak beropini tentang lapisan di atas HTTP, jadi jika Anda ingin dependency injection, decorator, dan sistem modul yang kaku, framework serba-lengkap seperti NestJS akan terasa lebih utuh. Tabel di bawah adalah cara saya memutuskan.
| Pertimbangan | Hono | Express |
|---|---|---|
| Dibangun di atas | Web Standards (fetch) | Modul http Node |
| Runtime | Workers, Bun, Node, Deno | Hanya Node |
| Dependensi | Nol | Beberapa transitif |
| Klien bertipe | RPC bawaan, tanpa codegen | Tidak ada |
| Router | RegExpRouter prakompilasi | Pencocokan middleware linier |
Tak satu pun dari ini membuat Express salah — ia membuat Hono default yang lebih baik untuk era edge. Jika API Anda mungkin hidup lebih lama dari runtime pertamanya, menulisnya terhadap Web Standards hari ini berarti Anda tidak menulis ulang di hari Anda pindah. Properti tunggal itu telah menghemat waktu saya lebih banyak daripada micro-benchmark mana pun.

