spinny:~/writing $ less hono-framework-guide.md
12Selama bertahun-tahun, memilih framework web JavaScript berarti menerima trade-off: Express universal tapi lambat dan terikat Node, Fastify cepat tapi hanya Node, Next.js lengkap tapi berat. Saat runtime edge — Cloudflare Workers, Deno Deploy, Bun, Vercel Edge — muncul, framework-framework itu menunjukkan batasannya: dependensi tidak kompatibel, bundle besar, API terikat pada `req`/`res` Node.34[Hono](https://hono.dev) (Jepang untuk "api" 🔥) adalah jawaban modern. Framework di bawah 14KB, sepenuhnya dibangun di atas Web Standards (`Request`, `Response`, `fetch`), berjalan di mana saja ada runtime JavaScript. Kode yang sama dideploy ke Cloudflare Workers, Bun, Deno, Node.js, Vercel, Netlify, dan AWS Lambda — tanpa perubahan.56## Mengapa Hono78Hono melakukan tiga hal lebih baik dari siapa pun:9101. **Performa.** `RegExpRouter` mengompilasi semua pola route ke satu regex, menghindari loop linear router tradisional. Benchmark melampaui 400.000 ops/s.112. **Portabilitas.** Web Standards berarti nol dependensi Node. `app.fetch` yang sama diekspor sebagai default di Cloudflare Worker, dilewatkan ke `Bun.serve`, dimount di server Deno, atau diadaptasi dengan `@hono/node-server`.123. **DX TypeScript-first.** Path parameter diinferensi sebagai literal type, klien RPC type-safe end-to-end, validator yang menginferensi tipe input dan output.1314```mermaid15graph LR16 Client[Client] -->|Request| App[app.fetch]17 App --> MW1[Middleware 1]18 MW1 --> MW2[Middleware 2]19 MW2 --> Router[RegExpRouter]20 Router --> Handler[Route Handler]21 Handler --> Context[c.json / c.text]22 Context -->|Response| Client23 App -.->|deploy| CF[Cloudflare Workers]24 App -.->|deploy| Bun[Bun]25 App -.->|deploy| Deno[Deno]26 App -.->|deploy| Node[Node.js]27 App -.->|deploy| Vercel[Vercel]28```2930## Memulai3132```bash33npm create hono@latest my-api34cd my-api35npm install36npm run dev37```3839Starter menanyakan template mana: `cloudflare-workers`, `bun`, `deno`, `nodejs`, `vercel`, `aws-lambda`, `nextjs` dan lainnya. Untuk uji coba cepat, bisa juga mulai dari satu file:4041```typescript42// src/index.ts43import { Hono } from 'hono'4445const app = new Hono()4647app.get('/', (c) => c.text('Hello Hono!'))4849export default app50```5152Di Cloudflare Workers itu cukup. Di Bun: `Bun.serve({ fetch: app.fetch, port: 3000 })`. Di Node: `serve({ fetch: app.fetch })` dari `@hono/node-server`.5354## Routing5556```typescript57import { Hono } from 'hono'5859const app = new Hono()6061app.get('/', (c) => c.text('Home'))62app.get('/posts/:id', (c) => {63 const id = c.req.param('id') // string, bertipe64 return c.json({ id })65})66app.get('/posts/:id/comments/:commentId', (c) => {67 const { id, commentId } = c.req.param()68 return c.json({ id, commentId })69})70app.get('/files/*', (c) => c.text('Wildcard'))71app.post('/posts', async (c) => {72 const body = await c.req.json()73 return c.json({ created: body }, 201)74})75```7677Parameter diinferensi sebagai literal type: TypeScript tahu `c.req.param('id')` mengembalikan `string` hanya jika Anda mendeklarasikan `:id` di pola. Salah ketik adalah error compile-time.7879### Mengelompokkan rute8081```typescript82// routes/posts.ts83import { Hono } from 'hono'8485const posts = new Hono()86posts.get('/', (c) => c.json({ posts: [] }))87posts.get('/:id', (c) => c.json({ id: c.req.param('id') }))88export default posts8990// src/index.ts91import { Hono } from 'hono'92import posts from './routes/posts'9394const app = new Hono()95app.route('/posts', posts)96```9798## Objek Context99100```typescript101app.post('/echo', async (c) => {102 const userAgent = c.req.header('User-Agent')103 const page = c.req.query('page')104 const body = await c.req.json()105 const env = c.env106107 c.set('requestId', crypto.randomUUID())108 const id = c.get('requestId')109110 c.header('X-Request-Id', id)111 c.status(200)112 return c.json({ userAgent, page, body, id })113})114```115116Metode response utama: `c.text`, `c.json`, `c.html`, `c.body` (raw), `c.redirect`.117118## Middleware: model bawang119120```typescript121import { Hono } from 'hono'122import { logger } from 'hono/logger'123import { cors } from 'hono/cors'124import { secureHeaders } from 'hono/secure-headers'125126const app = new Hono()127128app.use('*', logger())129app.use('*', secureHeaders())130app.use('/api/*', cors({ origin: 'https://spinny.dev' }))131132app.use('*', async (c, next) => {133 const start = performance.now()134 await next()135 c.header('X-Response-Time', `${performance.now() - start}ms`)136})137```138139`await next()` menyerahkan kontrol ke middleware berikutnya.140141### Middleware bawaan142143| Middleware | Fungsi |144|-----------|---------|145| `logger` | Log terstruktur metode, path, status, durasi |146| `cors` | CORS dapat dikonfigurasi per origin, metode, header |147| `csrf` | Proteksi CSRF berbasis origin |148| `secureHeaders` | Mengatur CSP, HSTS, X-Frame-Options |149| `bearerAuth` / `basicAuth` | Auth Bearer/Basic siap pakai |150| `jwt` | Verify/sign JWT dengan `jose` |151| `etag` | Membuat ETag dan menangani 304 |152| `cache` | Cache via Web Cache API |153| `compress` | gzip/deflate respons |154| `bodyLimit` | Menolak body di atas ambang |155| `timing` | Header Server-Timing untuk profiling |156157### Middleware kustom type-safe158159```typescript160import { createMiddleware } from 'hono/factory'161162type AuthVars = { userId: string; role: 'user' | 'admin' }163164export const requireAuth = createMiddleware<{ Variables: AuthVars }>(165 async (c, next) => {166 const token = c.req.header('Authorization')?.replace('Bearer ', '')167 if (!token) return c.json({ error: 'Unauthorized' }, 401)168169 const payload = await verifyJwt(token)170 c.set('userId', payload.sub)171 c.set('role', payload.role)172 await next()173 }174)175176app.get('/me', requireAuth, (c) => {177 const userId = c.var.userId178 return c.json({ userId })179})180```181182## Validasi dengan Zod183184```typescript185import { Hono } from 'hono'186import { zValidator } from '@hono/zod-validator'187import { z } from 'zod'188189const createPost = z.object({190 title: z.string().min(1).max(200),191 body: z.string().min(1),192 tags: z.array(z.string()).default([]),193})194195app.post(196 '/posts',197 zValidator('json', createPost),198 (c) => {199 const data = c.req.valid('json')200 return c.json({ ok: true, post: data }, 201)201 }202)203```204205## RPC: klien type-safe end-to-end206207```typescript208// server.ts209import { Hono } from 'hono'210import { zValidator } from '@hono/zod-validator'211import { z } from 'zod'212213const app = new Hono()214 .get('/posts/:id', (c) =>215 c.json({ id: c.req.param('id'), title: 'Hello' })216 )217 .post(218 '/posts',219 zValidator('json', z.object({ title: z.string(), body: z.string() })),220 (c) => c.json({ ok: true }, 201)221 )222223export type AppType = typeof app224export default app225```226227```typescript228// client.ts229import { hc } from 'hono/client'230import type { AppType } from './server'231232const client = hc<AppType>('https://api.spinny.dev')233234const res = await client.posts[':id'].$get({ param: { id: '42' } })235if (res.ok) {236 const data = await res.json()237 console.log(data.title)238}239240const created = await client.posts.$post({241 json: { title: 'Halo', body: 'Hono adalah api' },242})243```244245### Diskriminasi status code246247```typescript248.get('/posts/:id', (c) => {249 const post = findPost(c.req.param('id'))250 if (!post) return c.json({ error: 'not found' }, 404)251 return c.json({ post }, 200)252})253```254255```typescript256const res = await client.posts[':id'].$get({ param: { id } })257if (res.status === 404) {258 const { error } = await res.json()259}260if (res.status === 200) {261 const { post } = await res.json()262}263```264265## Router dan performa266267| Router | Kekuatan | Kapan digunakan |268|--------|-----------|-------------|269| `RegExpRouter` | Kecepatan maksimal, regex terkompilasi | Default untuk sebagian besar API |270| `TrieRouter` | Mendukung semua pola | Pola kompleks yang tidak ditangani RegExp |271| `SmartRouter` | Memilih yang terbaik otomatis | Default yang direkomendasikan |272| `LinearRouter` | Registrasi sangat cepat | Worker one-shot, cold start kritis |273| `PatternRouter` | Bundle minimal (<15KB) | Batasan ukuran ekstrem |274275```typescript276import { Hono } from 'hono'277import { LinearRouter } from 'hono/router/linear-router'278279const app = new Hono({ router: new LinearRouter() })280```281282## Deploy multi-runtime283284### Cloudflare Workers285286```typescript287import { Hono } from 'hono'288289type Bindings = { MY_KV: KVNamespace; DB: D1Database }290const app = new Hono<{ Bindings: Bindings }>()291292app.get('/cache/:key', async (c) => {293 const value = await c.env.MY_KV.get(c.req.param('key'))294 return c.json({ value })295})296297export default app298```299300Deploy: `npx wrangler deploy`.301302### Bun303304```typescript305import { Hono } from 'hono'306const app = new Hono()307app.get('/', (c) => c.text('Bun + Hono'))308309Bun.serve({ fetch: app.fetch, port: 3000 })310```311312### Node.js313314```typescript315import { serve } from '@hono/node-server'316import { Hono } from 'hono'317318const app = new Hono()319app.get('/', (c) => c.text('Node + Hono'))320321serve({ fetch: app.fetch, port: 3000 })322```323324### Deno325326```typescript327import { Hono } from 'jsr:@hono/hono'328const app = new Hono()329app.get('/', (c) => c.text('Deno + Hono'))330Deno.serve(app.fetch)331```332333### Vercel334335```typescript336// api/[[...route]].ts337import { Hono } from 'hono'338import { handle } from 'hono/vercel'339340const app = new Hono().basePath('/api')341app.get('/hello', (c) => c.json({ msg: 'Hello from Vercel' }))342343export const GET = handle(app)344export const POST = handle(app)345```346347## Contoh praktis: REST API dengan auth dan DB348349```typescript350import { Hono } from 'hono'351import { jwt } from 'hono/jwt'352import { logger } from 'hono/logger'353import { cors } from 'hono/cors'354import { zValidator } from '@hono/zod-validator'355import { z } from 'zod'356357type Bindings = { DB: D1Database; JWT_SECRET: string }358type Variables = { jwtPayload: { sub: string } }359360const app = new Hono<{ Bindings: Bindings; Variables: Variables }>()361362app.use('*', logger())363app.use('/api/*', cors({ origin: 'https://spinny.dev', credentials: true }))364365const auth = (c: any, next: any) =>366 jwt({ secret: c.env.JWT_SECRET })(c, next)367368const api = app.basePath('/api')369370api.get('/posts', async (c) => {371 const { results } = await c.env.DB372 .prepare('SELECT id, title, created_at FROM posts ORDER BY created_at DESC LIMIT 50')373 .all()374 return c.json({ posts: results })375})376377api.post(378 '/posts',379 auth,380 zValidator('json', z.object({381 title: z.string().min(1).max(200),382 body: z.string().min(1),383 })),384 async (c) => {385 const { title, body } = c.req.valid('json')386 const userId = c.var.jwtPayload.sub387 const result = await c.env.DB388 .prepare('INSERT INTO posts (title, body, author_id) VALUES (?, ?, ?) RETURNING id')389 .bind(title, body, userId)390 .first<{ id: number }>()391 return c.json({ id: result?.id }, 201)392 }393)394395api.onError((err, c) => {396 console.error(err)397 return c.json({ error: 'Internal error' }, 500)398})399400export type AppType = typeof api401export default app402```403404## Testing405406```typescript407import { describe, it, expect } from 'vitest'408import app from '../src/index'409410describe('GET /api/posts', () => {411 it('mengembalikan daftar post', async () => {412 const res = await app.request('/api/posts')413 expect(res.status).toBe(200)414 const body = await res.json()415 expect(body.posts).toBeInstanceOf(Array)416 })417})418```419420## Praktik terbaik421422### 1. Rangkai definisi rute423424```typescript425const app = new Hono()426 .get('/posts', handler1)427 .post('/posts', handler2)428 .get('/posts/:id', handler3)429```430431### 2. Ekspor tipe, bukan implementasi432433Klien harus mengimpor `AppType`. `import type` memastikan build frontend tidak menyertakan kode backend.434435### 3. Satu router per domain436437Sub-app untuk `posts`, `users`, `webhooks`. Komposisi via `app.route()`.438439### 4. Validasi di tepi, selalu440441Setiap input eksternal harus melewati `zValidator`.442443### 5. Bersandar pada bindings, bukan klien global444445Di Cloudflare, akses KV/D1/R2 via `c.env`.446447### 6. Ukur sebelum mengoptimalkan router448449`SmartRouter` default cocok untuk 95% kasus.450451## Kesimpulan452453Hono telah menjadi standar de facto pada 2026 untuk membangun API edge-ready dengan TypeScript. Kombinasi Web Standards, performa, type safety, dan portabilitas memecahkan tepat masalah-masalah yang menahan framework tradisional.454455> **Checklist memulai:**456>457> - [x] `npm create hono@latest` dan pilih template runtime Anda458> - [x] Definisikan rute dengan chaining (`.get(...).post(...)`)459> - [x] Tambahkan `logger`, `cors`, `secureHeaders` sebagai middleware global460> - [x] Validasi setiap input dengan `@hono/zod-validator`461> - [x] Ekspor `AppType` dan konsumsi API dengan klien `hc` type-safe462> - [x] Tulis tes dengan `app.request()` — tanpa server HTTP463> - [x] Deploy dengan `wrangler deploy` (CF), `vercel deploy`, atau bundler runtime Anda464
:Hono: Framework Web Ultracepat Berbasis Web Standardslines 1-464 (END) — press q to close