spinny:~/writing $ vim hono-framework-guide.md
1~2Sa loob ng maraming taon, ang pagpili ng JavaScript web framework ay nangangahulugang pagtanggap ng trade-off: ang Express ay universal pero mabagal at nakatali sa Node, ang Fastify ay mabilis pero Node lang, ang Next.js ay full-featured pero mabigat. Nang dumating ang edge runtimes — Cloudflare Workers, Deno Deploy, Bun, Vercel Edge — ipinakita ng mga framework na iyon ang kanilang mga limitasyon.3~4Ang [Hono](https://hono.dev) (Hapon para sa "apoy" 🔥) ay ang modernong sagot. Isang framework na mababa sa 14KB, ganap na nakabase sa Web Standards (`Request`, `Response`, `fetch`), na tumatakbo saanman may JavaScript runtime. Ang parehong code ay nade-deploy sa Cloudflare Workers, Bun, Deno, Node.js, Vercel, Netlify at AWS Lambda — nang walang pagbabago.5~6## Bakit Hono7~81. **Performance.** Pinagsasama-sama ng `RegExpRouter` ang lahat ng route patterns sa isang regex. Lumalampas ang benchmarks sa 400,000 ops/s.92. **Portability.** Web Standards ay nangangahulugang zero Node dependencies.103. **TypeScript-first DX.** Ang path parameters ay na-i-infer bilang literal types, end-to-end type-safe RPC client.11~12```mermaid13graph LR14 Client[Client] -->|Request| App[app.fetch]15 App --> MW1[Middleware 1]16 MW1 --> MW2[Middleware 2]17 MW2 --> Router[RegExpRouter]18 Router --> Handler[Route Handler]19 Handler --> Context[c.json / c.text]20 Context -->|Response| Client21 App -.->|deploy| CF[Cloudflare Workers]22 App -.->|deploy| Bun[Bun]23 App -.->|deploy| Deno[Deno]24 App -.->|deploy| Node[Node.js]25 App -.->|deploy| Vercel[Vercel]26```27~28## Pagsisimula29~30```bash31npm create hono@latest my-api32cd my-api33npm install34npm run dev35```36~37```typescript38// src/index.ts39import { Hono } from 'hono'40~41const app = new Hono()42~43app.get('/', (c) => c.text('Hello Hono!'))44~45export default app46```47~48Sa Cloudflare Workers, sapat na ito. Sa Bun: `Bun.serve({ fetch: app.fetch, port: 3000 })`. Sa Node: `serve({ fetch: app.fetch })` mula sa `@hono/node-server`.49~50## Routing51~52```typescript53import { Hono } from 'hono'54~55const app = new Hono()56~57app.get('/', (c) => c.text('Home'))58app.get('/posts/:id', (c) => {59 const id = c.req.param('id')60 return c.json({ id })61})62app.get('/posts/:id/comments/:commentId', (c) => {63 const { id, commentId } = c.req.param()64 return c.json({ id, commentId })65})66app.get('/files/*', (c) => c.text('Wildcard'))67app.post('/posts', async (c) => {68 const body = await c.req.json()69 return c.json({ created: body }, 201)70})71```72~73### Pag-grupo ng routes74~75```typescript76// routes/posts.ts77import { Hono } from 'hono'78~79const posts = new Hono()80posts.get('/', (c) => c.json({ posts: [] }))81posts.get('/:id', (c) => c.json({ id: c.req.param('id') }))82export default posts83~84// src/index.ts85import { Hono } from 'hono'86import posts from './routes/posts'87~88const app = new Hono()89app.route('/posts', posts)90```91~92## Ang Context object93~94```typescript95app.post('/echo', async (c) => {96 const userAgent = c.req.header('User-Agent')97 const page = c.req.query('page')98 const body = await c.req.json()99 const env = c.env100~101 c.set('requestId', crypto.randomUUID())102 const id = c.get('requestId')103~104 c.header('X-Request-Id', id)105 c.status(200)106 return c.json({ userAgent, page, body, id })107})108```109~110## Middleware: ang onion model111~112```typescript113import { Hono } from 'hono'114import { logger } from 'hono/logger'115import { cors } from 'hono/cors'116import { secureHeaders } from 'hono/secure-headers'117~118const app = new Hono()119~120app.use('*', logger())121app.use('*', secureHeaders())122app.use('/api/*', cors({ origin: 'https://spinny.dev' }))123~124app.use('*', async (c, next) => {125 const start = performance.now()126 await next()127 c.header('X-Response-Time', `${performance.now() - start}ms`)128})129```130~131### Built-in middleware132~133| Middleware | Layunin |134|-----------|---------|135| `logger` | Structured logs ng method, path, status, duration |136| `cors` | CORS na configurable ayon sa origin, methods, headers |137| `csrf` | Origin-based CSRF protection |138| `secureHeaders` | Nagse-set ng CSP, HSTS, X-Frame-Options |139| `bearerAuth` / `basicAuth` | Out-of-the-box Bearer/Basic auth |140| `jwt` | JWT verify/sign gamit ang `jose` |141| `etag` | Bumubuo ng ETag at hinahawakan ang 304 |142| `cache` | Cache sa pamamagitan ng Web Cache API |143| `compress` | gzip/deflate sa response |144| `bodyLimit` | Tinatanggihan ang bodies na lampas sa threshold |145| `timing` | Server-Timing header para sa profiling |146~147### Type-safe custom middleware148~149```typescript150import { createMiddleware } from 'hono/factory'151~152type AuthVars = { userId: string; role: 'user' | 'admin' }153~154export const requireAuth = createMiddleware<{ Variables: AuthVars }>(155 async (c, next) => {156 const token = c.req.header('Authorization')?.replace('Bearer ', '')157 if (!token) return c.json({ error: 'Unauthorized' }, 401)158~159 const payload = await verifyJwt(token)160 c.set('userId', payload.sub)161 c.set('role', payload.role)162 await next()163 }164)165~166app.get('/me', requireAuth, (c) => {167 const userId = c.var.userId168 return c.json({ userId })169})170```171~172## Validation gamit ang Zod173~174```typescript175import { Hono } from 'hono'176import { zValidator } from '@hono/zod-validator'177import { z } from 'zod'178~179const createPost = z.object({180 title: z.string().min(1).max(200),181 body: z.string().min(1),182 tags: z.array(z.string()).default([]),183})184~185app.post(186 '/posts',187 zValidator('json', createPost),188 (c) => {189 const data = c.req.valid('json')190 return c.json({ ok: true, post: data }, 201)191 }192)193```194~195## RPC: end-to-end type-safe client196~197```typescript198// server.ts199import { Hono } from 'hono'200import { zValidator } from '@hono/zod-validator'201import { z } from 'zod'202~203const app = new Hono()204 .get('/posts/:id', (c) =>205 c.json({ id: c.req.param('id'), title: 'Hello' })206 )207 .post(208 '/posts',209 zValidator('json', z.object({ title: z.string(), body: z.string() })),210 (c) => c.json({ ok: true }, 201)211 )212~213export type AppType = typeof app214export default app215```216~217```typescript218// client.ts219import { hc } from 'hono/client'220import type { AppType } from './server'221~222const client = hc<AppType>('https://api.spinny.dev')223~224const res = await client.posts[':id'].$get({ param: { id: '42' } })225if (res.ok) {226 const data = await res.json()227 console.log(data.title)228}229~230const created = await client.posts.$post({231 json: { title: 'Kumusta', body: 'Ang Hono ay isang apoy' },232})233```234~235### Status code discrimination236~237```typescript238.get('/posts/:id', (c) => {239 const post = findPost(c.req.param('id'))240 if (!post) return c.json({ error: 'not found' }, 404)241 return c.json({ post }, 200)242})243```244~245```typescript246const res = await client.posts[':id'].$get({ param: { id } })247if (res.status === 404) {248 const { error } = await res.json()249}250if (res.status === 200) {251 const { post } = await res.json()252}253```254~255## Routers at performance256~257| Router | Lakas | Kailan gamitin |258|--------|-----------|-------------|259| `RegExpRouter` | Pinakamabilis, compiled regex | Default para sa karamihan ng APIs |260| `TrieRouter` | Sumusuporta sa lahat ng patterns | Kumplikadong patterns na hindi kayang hawakan ng RegExp |261| `SmartRouter` | Awtomatikong pumipili ng pinakamahusay | Inirerekomendang default |262| `LinearRouter` | Ultra-bilis na registration | One-shot workers, kritikal na cold start |263| `PatternRouter` | Pinakamaliit na bundle (<15KB) | Sukdulang limitasyon sa laki |264~265```typescript266import { Hono } from 'hono'267import { LinearRouter } from 'hono/router/linear-router'268~269const app = new Hono({ router: new LinearRouter() })270```271~272## Multi-runtime deploy273~274### Cloudflare Workers275~276```typescript277import { Hono } from 'hono'278~279type Bindings = { MY_KV: KVNamespace; DB: D1Database }280const app = new Hono<{ Bindings: Bindings }>()281~282app.get('/cache/:key', async (c) => {283 const value = await c.env.MY_KV.get(c.req.param('key'))284 return c.json({ value })285})286~287export default app288```289~290Deploy: `npx wrangler deploy`.291~292### Bun293~294```typescript295import { Hono } from 'hono'296const app = new Hono()297app.get('/', (c) => c.text('Bun + Hono'))298~299Bun.serve({ fetch: app.fetch, port: 3000 })300```301~302### Node.js303~304```typescript305import { serve } from '@hono/node-server'306import { Hono } from 'hono'307~308const app = new Hono()309app.get('/', (c) => c.text('Node + Hono'))310~311serve({ fetch: app.fetch, port: 3000 })312```313~314### Deno315~316```typescript317import { Hono } from 'jsr:@hono/hono'318const app = new Hono()319app.get('/', (c) => c.text('Deno + Hono'))320Deno.serve(app.fetch)321```322~323### Vercel324~325```typescript326// api/[[...route]].ts327import { Hono } from 'hono'328import { handle } from 'hono/vercel'329~330const app = new Hono().basePath('/api')331app.get('/hello', (c) => c.json({ msg: 'Hello from Vercel' }))332~333export const GET = handle(app)334export const POST = handle(app)335```336~337## Praktikal na halimbawa: REST API na may auth at DB338~339```typescript340import { Hono } from 'hono'341import { jwt } from 'hono/jwt'342import { logger } from 'hono/logger'343import { cors } from 'hono/cors'344import { zValidator } from '@hono/zod-validator'345import { z } from 'zod'346~347type Bindings = { DB: D1Database; JWT_SECRET: string }348type Variables = { jwtPayload: { sub: string } }349~350const app = new Hono<{ Bindings: Bindings; Variables: Variables }>()351~352app.use('*', logger())353app.use('/api/*', cors({ origin: 'https://spinny.dev', credentials: true }))354~355const auth = (c: any, next: any) =>356 jwt({ secret: c.env.JWT_SECRET })(c, next)357~358const api = app.basePath('/api')359~360api.get('/posts', async (c) => {361 const { results } = await c.env.DB362 .prepare('SELECT id, title, created_at FROM posts ORDER BY created_at DESC LIMIT 50')363 .all()364 return c.json({ posts: results })365})366~367api.post(368 '/posts',369 auth,370 zValidator('json', z.object({371 title: z.string().min(1).max(200),372 body: z.string().min(1),373 })),374 async (c) => {375 const { title, body } = c.req.valid('json')376 const userId = c.var.jwtPayload.sub377 const result = await c.env.DB378 .prepare('INSERT INTO posts (title, body, author_id) VALUES (?, ?, ?) RETURNING id')379 .bind(title, body, userId)380 .first<{ id: number }>()381 return c.json({ id: result?.id }, 201)382 }383)384~385api.onError((err, c) => {386 console.error(err)387 return c.json({ error: 'Internal error' }, 500)388})389~390export type AppType = typeof api391export default app392```393~394## Pagsubok395~396```typescript397import { describe, it, expect } from 'vitest'398import app from '../src/index'399~400describe('GET /api/posts', () => {401 it('nagbabalik ng listahan ng mga post', async () => {402 const res = await app.request('/api/posts')403 expect(res.status).toBe(200)404 const body = await res.json()405 expect(body.posts).toBeInstanceOf(Array)406 })407})408```409~410## Best practices411~412### 1. I-chain ang mga route definition413~414```typescript415const app = new Hono()416 .get('/posts', handler1)417 .post('/posts', handler2)418 .get('/posts/:id', handler3)419```420~421### 2. I-export ang type, hindi ang implementation422~423Ang client ay dapat mag-import ng `AppType`.424~425### 3. Isang router bawat domain426~427Sub-app para sa `posts`, `users`, `webhooks`.428~429### 4. Validation sa gilid, palagi430~431Bawat external input ay dapat dumaan sa `zValidator`.432~433### 5. Umasa sa bindings, hindi sa global clients434~435Sa Cloudflare, i-access ang KV/D1/R2 sa pamamagitan ng `c.env`.436~437### 6. Sukatin bago i-optimize ang router438~439Ang default na `SmartRouter` ay angkop sa 95% ng mga kaso.440~441## Konklusyon442~443Naging de facto standard ang Hono noong 2026 para sa pagbuo ng edge-ready APIs sa TypeScript.444~445> **Checklist sa pagsisimula:**446>447> - [x] `npm create hono@latest` at piliin ang iyong runtime template448> - [x] I-define ang routes na may chaining (`.get(...).post(...)`)449> - [x] Idagdag ang `logger`, `cors`, `secureHeaders` bilang global middleware450> - [x] I-validate ang bawat input gamit ang `@hono/zod-validator`451> - [x] I-export ang `AppType` at konsumihin ang API gamit ang type-safe `hc` client452> - [x] Sumulat ng tests gamit ang `app.request()` — walang HTTP server na kailangan453> - [x] I-deploy gamit ang `wrangler deploy` (CF), `vercel deploy` o ang bundler ng iyong runtime454~
NORMAL · hono-framework-guide.md [readonly]454 lines · :q to close