spinny:~/writing $ less hono-framework-guide.md
12Po leta vyber JavaScript webového frameworku znamenal prijmout kompromis: Express byl univerzalni, ale pomaly a vazany na Node, Fastify rychly ale jen Node, Next.js plnohodnotny ale tezky. Kdyz prisly edge runtime — Cloudflare Workers, Deno Deploy, Bun, Vercel Edge — tyto frameworky ukazaly sve limity.34[Hono](https://hono.dev) (japonsky "plamen" 🔥) je moderni odpoved. Framework pod 14KB, plne postaveny na Web Standards (`Request`, `Response`, `fetch`), bezi vsude, kde existuje JavaScript runtime. Stejny kod se deployuje na Cloudflare Workers, Bun, Deno, Node.js, Vercel, Netlify a AWS Lambda — bez zmen.56## Proc Hono781. **Vykon.** `RegExpRouter` kompiluje vsechny route patterny do jednoho regexu. Benchmarky presahuji 400 000 ops/s.92. **Prenositelnost.** Web Standards znamena nulove zavislosti na Node.103. **TypeScript-first DX.** Path parametry odvozeny jako literal typy, end-to-end type-safe RPC klient.1112```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```2728## Zacatek2930```bash31npm create hono@latest my-api32cd my-api33npm install34npm run dev35```3637```typescript38// src/index.ts39import { Hono } from 'hono'4041const app = new Hono()4243app.get('/', (c) => c.text('Hello Hono!'))4445export default app46```4748Na Cloudflare Workers tohle staci. Na Bun: `Bun.serve({ fetch: app.fetch, port: 3000 })`. Na Node: `serve({ fetch: app.fetch })` z `@hono/node-server`.4950## Routovani5152```typescript53import { Hono } from 'hono'5455const app = new Hono()5657app.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```7273### Skupiny rout7475```typescript76// routes/posts.ts77import { Hono } from 'hono'7879const posts = new Hono()80posts.get('/', (c) => c.json({ posts: [] }))81posts.get('/:id', (c) => c.json({ id: c.req.param('id') }))82export default posts8384// src/index.ts85import { Hono } from 'hono'86import posts from './routes/posts'8788const app = new Hono()89app.route('/posts', posts)90```9192## Objekt Context9394```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.env100101 c.set('requestId', crypto.randomUUID())102 const id = c.get('requestId')103104 c.header('X-Request-Id', id)105 c.status(200)106 return c.json({ userAgent, page, body, id })107})108```109110## Middleware: cibulovy model111112```typescript113import { Hono } from 'hono'114import { logger } from 'hono/logger'115import { cors } from 'hono/cors'116import { secureHeaders } from 'hono/secure-headers'117118const app = new Hono()119120app.use('*', logger())121app.use('*', secureHeaders())122app.use('/api/*', cors({ origin: 'https://spinny.dev' }))123124app.use('*', async (c, next) => {125 const start = performance.now()126 await next()127 c.header('X-Response-Time', `${performance.now() - start}ms`)128})129```130131### Vestavene middleware132133| Middleware | Ucel |134|-----------|---------|135| `logger` | Strukturovane logy metody, cesty, statusu, doby |136| `cors` | CORS konfigurovatelny dle origin, metod, headeru |137| `csrf` | Origin-zalozena ochrana CSRF |138| `secureHeaders` | Nastavuje CSP, HSTS, X-Frame-Options |139| `bearerAuth` / `basicAuth` | Hotova Bearer/Basic auth |140| `jwt` | JWT verifikace/podpis pres `jose` |141| `etag` | Generuje ETag a obsluhuje 304 |142| `cache` | Cache pres Web Cache API |143| `compress` | gzip/deflate odpovedi |144| `bodyLimit` | Odmita body nad limit |145| `timing` | Header Server-Timing pro profilovani |146147### Type-safe vlastni middleware148149```typescript150import { createMiddleware } from 'hono/factory'151152type AuthVars = { userId: string; role: 'user' | 'admin' }153154export 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)158159 const payload = await verifyJwt(token)160 c.set('userId', payload.sub)161 c.set('role', payload.role)162 await next()163 }164)165166app.get('/me', requireAuth, (c) => {167 const userId = c.var.userId168 return c.json({ userId })169})170```171172## Validace s Zod173174```typescript175import { Hono } from 'hono'176import { zValidator } from '@hono/zod-validator'177import { z } from 'zod'178179const 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})184185app.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```194195## RPC: type-safe klient end-to-end196197```typescript198// server.ts199import { Hono } from 'hono'200import { zValidator } from '@hono/zod-validator'201import { z } from 'zod'202203const 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 )212213export type AppType = typeof app214export default app215```216217```typescript218// client.ts219import { hc } from 'hono/client'220import type { AppType } from './server'221222const client = hc<AppType>('https://api.spinny.dev')223224const res = await client.posts[':id'].$get({ param: { id: '42' } })225if (res.ok) {226 const data = await res.json()227 console.log(data.title)228}229230const created = await client.posts.$post({231 json: { title: 'Ahoj', body: 'Hono je plamen' },232})233```234235### Diskriminace status kodu236237```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```244245```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```254255## Routery a vykon256257| Router | Silne stranky | Kdy pouzit |258|--------|-----------|-------------|259| `RegExpRouter` | Maximalni rychlost, kompilovany regex | Default pro vetsinu API |260| `TrieRouter` | Podporuje vsechny patterny | Slozite patterny, ktere RegExp nezvlada |261| `SmartRouter` | Voli nejlepsi automaticky | Doporuceny default |262| `LinearRouter` | Ultrarychla registrace | One-shot workery, kriticky cold start |263| `PatternRouter` | Minimalni bundle (<15KB) | Extremni omezeni velikosti |264265```typescript266import { Hono } from 'hono'267import { LinearRouter } from 'hono/router/linear-router'268269const app = new Hono({ router: new LinearRouter() })270```271272## Multi-runtime deploy273274### Cloudflare Workers275276```typescript277import { Hono } from 'hono'278279type Bindings = { MY_KV: KVNamespace; DB: D1Database }280const app = new Hono<{ Bindings: Bindings }>()281282app.get('/cache/:key', async (c) => {283 const value = await c.env.MY_KV.get(c.req.param('key'))284 return c.json({ value })285})286287export default app288```289290Deploy: `npx wrangler deploy`.291292### Bun293294```typescript295import { Hono } from 'hono'296const app = new Hono()297app.get('/', (c) => c.text('Bun + Hono'))298299Bun.serve({ fetch: app.fetch, port: 3000 })300```301302### Node.js303304```typescript305import { serve } from '@hono/node-server'306import { Hono } from 'hono'307308const app = new Hono()309app.get('/', (c) => c.text('Node + Hono'))310311serve({ fetch: app.fetch, port: 3000 })312```313314### Deno315316```typescript317import { Hono } from 'jsr:@hono/hono'318const app = new Hono()319app.get('/', (c) => c.text('Deno + Hono'))320Deno.serve(app.fetch)321```322323### Vercel324325```typescript326// api/[[...route]].ts327import { Hono } from 'hono'328import { handle } from 'hono/vercel'329330const app = new Hono().basePath('/api')331app.get('/hello', (c) => c.json({ msg: 'Hello from Vercel' }))332333export const GET = handle(app)334export const POST = handle(app)335```336337## Prakticky priklad: REST API s auth a DB338339```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'346347type Bindings = { DB: D1Database; JWT_SECRET: string }348type Variables = { jwtPayload: { sub: string } }349350const app = new Hono<{ Bindings: Bindings; Variables: Variables }>()351352app.use('*', logger())353app.use('/api/*', cors({ origin: 'https://spinny.dev', credentials: true }))354355const auth = (c: any, next: any) =>356 jwt({ secret: c.env.JWT_SECRET })(c, next)357358const api = app.basePath('/api')359360api.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})366367api.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)384385api.onError((err, c) => {386 console.error(err)387 return c.json({ error: 'Internal error' }, 500)388})389390export type AppType = typeof api391export default app392```393394## Testovani395396```typescript397import { describe, it, expect } from 'vitest'398import app from '../src/index'399400describe('GET /api/posts', () => {401 it('vraci seznam prispevku', 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```409410## Nejlepsi praktiky411412### 1. Retezte definice rout413414```typescript415const app = new Hono()416 .get('/posts', handler1)417 .post('/posts', handler2)418 .get('/posts/:id', handler3)419```420421### 2. Exportuj typ, ne implementaci422423Klient musi importovat `AppType`.424425### 3. Jeden router na domenu426427Sub-app pro `posts`, `users`, `webhooks`.428429### 4. Validace na hranici, vzdy430431Kazdy externi input musi projit pres `zValidator`.432433### 5. Spolehni se na bindings, ne na globalni klienty434435Na Cloudflare pristupuj ke KV/D1/R2 pres `c.env`.436437### 6. Mer pred optimalizaci routeru438439Defaultni `SmartRouter` vyhovuje v 95% pripadu.440441## Zaver442443Hono se v roce 2026 stalo de facto standardem pro stavbu edge-ready API v TypeScriptu.444445> **Checklist na start:**446>447> - [x] `npm create hono@latest` a vyber sablonu runtime448> - [x] Definuj routy retezenim (`.get(...).post(...)`)449> - [x] Pridej `logger`, `cors`, `secureHeaders` jako globalni middleware450> - [x] Validuj kazdy input pres `@hono/zod-validator`451> - [x] Exportuj `AppType` a konzumuj API pres type-safe `hc` klienta452> - [x] Pis testy pres `app.request()` — bez HTTP serveru453> - [x] Deployuj pres `wrangler deploy` (CF), `vercel deploy` nebo bundler tveho runtime454
:Hono: ultrarychly webovy framework postaveny na Web Standardslines 1-454 (END) — press q to close