Yıllarca bir JavaScript web framework''ü seçmek bir taviz vermek anlamına geliyordu: Express evrenseldi ama yavaş ve Node''a bağlıydı, Fastify hızlıydı ama yalnızca Node, Next.js eksiksizdi ama ağırdı. Edge runtime''ları — Cloudflare Workers, Deno Deploy, Bun, Vercel Edge — geldiğinde, bu framework''ler sınırlarını gösterdi: uyumsuz bağımlılıklar, devasa bundle''lar, Node''un req/res''sine bağlı API''ler.
Hono (Japoncada "alev" 🔥) modern cevap. Tamamen Web Standartları (Request, Response, fetch) üzerine kurulu 14KB altı bir framework, JavaScript runtime''i olan her yerde çalışır. Aynı kod Cloudflare Workers, Bun, Deno, Node.js, Vercel, Netlify ve AWS Lambda''ya değişiklik olmadan deploy edilir.
Neden Hono
- Performans.
RegExpRoutertüm route pattern''lerini tek bir regex''e derler, geleneksel router''ların doğrusal döngülerinden kaçınır. Benchmark''lar saniyede 400.000 op''u aşıyor. - Taşınabilirlik. Web Standartları sıfır Node bağımlılığı demektir. Aynı
app.fetchCloudflare Worker''da default export edilir,Bun.serve''e geçirilir, Deno sunucusuna mount edilir veya@hono/node-serverile uyarlanır. - TypeScript-first DX. Literal tipler olarak çıkarsanan path parametreleri, uçtan uca tip güvenli RPC istemcisi.
Başlamak
npm create hono@latest my-api cd my-api npm install npm run dev
Starter hangi şablonu kullanacağını sorar: cloudflare-workers, bun, deno, nodejs, vercel, aws-lambda, nextjs ve daha fazlası. Hızlıca denemek için tek bir dosyadan da başlayabilirsiniz:
// src/index.ts import { Hono } from 'hono' const app = new Hono() app.get('/', (c) => c.text('Hello Hono!')) export default app
Cloudflare Workers''ta bu yeterli. Bun''da: Bun.serve({ fetch: app.fetch, port: 3000 }). Node''da: @hono/node-server üzerinden serve({ fetch: app.fetch }).
Routing
import { Hono } from 'hono' const app = new Hono() app.get('/', (c) => c.text('Home')) app.get('/posts/:id', (c) => { const id = c.req.param('id') // string, tipli return c.json({ id }) }) app.get('/posts/:id/comments/:commentId', (c) => { const { id, commentId } = c.req.param() return c.json({ id, commentId }) }) app.get('/files/*', (c) => c.text('Wildcard')) app.post('/posts', async (c) => { const body = await c.req.json() return c.json({ created: body }, 201) })
Parametreler literal tip olarak çıkarsanır. Yanlış yazım derleme zamanı hatasıdır.
Route gruplama
// routes/posts.ts import { Hono } from 'hono' const posts = new Hono() posts.get('/', (c) => c.json({ posts: [] })) posts.get('/:id', (c) => c.json({ id: c.req.param('id') })) export default posts // src/index.ts import { Hono } from 'hono' import posts from './routes/posts' const app = new Hono() app.route('/posts', posts)
Context nesnesi
app.post('/echo', async (c) => { const userAgent = c.req.header('User-Agent') const page = c.req.query('page') const body = await c.req.json() const env = c.env c.set('requestId', crypto.randomUUID()) const id = c.get('requestId') c.header('X-Request-Id', id) c.status(200) return c.json({ userAgent, page, body, id }) })
Middleware: soğan modeli
import { Hono } from 'hono' import { logger } from 'hono/logger' import { cors } from 'hono/cors' import { secureHeaders } from 'hono/secure-headers' const app = new Hono() app.use('*', logger()) app.use('*', secureHeaders()) app.use('/api/*', cors({ origin: 'https://spinny.dev' })) app.use('*', async (c, next) => { const start = performance.now() await next() c.header('X-Response-Time', `${performance.now() - start}ms`) })
Yerleşik middleware
| Middleware | İşlevi |
|---|---|
logger | Metod, path, durum, süreyi yapılandırılmış loglar |
cors | Origin, metod, header''a göre yapılandırılabilir CORS |
csrf | Origin tabanlı CSRF koruması |
secureHeaders | CSP, HSTS, X-Frame-Options ayarlar |
bearerAuth / basicAuth | Hazır Bearer/Basic kimlik doğrulama |
jwt | jose ile JWT doğrulama/imzalama |
etag | ETag üretir, 304''ü işler |
cache | Web Cache API üzerinden önbellek |
compress | Yanıt için gzip/deflate |
bodyLimit | Eşik üstü body''leri reddeder |
timing | Profil için Server-Timing header''ı |
Tip güvenli özel middleware
import { createMiddleware } from 'hono/factory' type AuthVars = { userId: string; role: 'user' | 'admin' } export const requireAuth = createMiddleware<{ Variables: AuthVars }>( async (c, next) => { const token = c.req.header('Authorization')?.replace('Bearer ', '') if (!token) return c.json({ error: 'Unauthorized' }, 401) const payload = await verifyJwt(token) c.set('userId', payload.sub) c.set('role', payload.role) await next() } ) app.get('/me', requireAuth, (c) => { const userId = c.var.userId return c.json({ userId }) })
Zod ile doğrulama
import { Hono } from 'hono' import { zValidator } from '@hono/zod-validator' import { z } from 'zod' const createPost = z.object({ title: z.string().min(1).max(200), body: z.string().min(1), tags: z.array(z.string()).default([]), }) app.post( '/posts', zValidator('json', createPost), (c) => { const data = c.req.valid('json') return c.json({ ok: true, post: data }, 201) } )
RPC: uçtan uca tip güvenli istemci
// server.ts import { Hono } from 'hono' import { zValidator } from '@hono/zod-validator' import { z } from 'zod' const app = new Hono() .get('/posts/:id', (c) => c.json({ id: c.req.param('id'), title: 'Hello' }) ) .post( '/posts', zValidator('json', z.object({ title: z.string(), body: z.string() })), (c) => c.json({ ok: true }, 201) ) export type AppType = typeof app export default app
// client.ts import { hc } from 'hono/client' import type { AppType } from './server' const client = hc<AppType>('https://api.spinny.dev') const res = await client.posts[':id'].$get({ param: { id: '42' } }) if (res.ok) { const data = await res.json() console.log(data.title) } const created = await client.posts.$post({ json: { title: 'Merhaba', body: 'Hono bir alev' }, })
Durum kodu ayrıştırma
.get('/posts/:id', (c) => { const post = findPost(c.req.param('id')) if (!post) return c.json({ error: 'not found' }, 404) return c.json({ post }, 200) })
const res = await client.posts[':id'].$get({ param: { id } }) if (res.status === 404) { const { error } = await res.json() } if (res.status === 200) { const { post } = await res.json() }
Router''lar ve performans
| Router | Güçlü yönler | Ne zaman |
|---|---|---|
RegExpRouter | Maksimum hız, derlenmiş regex | Çoğu API için varsayılan |
TrieRouter | Tüm pattern''leri destekler | RegExp''in işleyemediği karmaşık pattern''ler |
SmartRouter | En iyiyi otomatik seçer | Önerilen varsayılan |
LinearRouter | Ultra hızlı kayıt | Tek atışlık worker, kritik soğuk başlangıç |
PatternRouter | Minimal bundle (<15KB) | Aşırı boyut kısıtları |
import { Hono } from 'hono' import { LinearRouter } from 'hono/router/linear-router' const app = new Hono({ router: new LinearRouter() })
Çoklu runtime deploy
Cloudflare Workers
import { Hono } from 'hono' type Bindings = { MY_KV: KVNamespace; DB: D1Database } const app = new Hono<{ Bindings: Bindings }>() app.get('/cache/:key', async (c) => { const value = await c.env.MY_KV.get(c.req.param('key')) return c.json({ value }) }) export default app
Deploy: npx wrangler deploy.
Bun
import { Hono } from 'hono' const app = new Hono() app.get('/', (c) => c.text('Bun + Hono')) Bun.serve({ fetch: app.fetch, port: 3000 })
Node.js
import { serve } from '@hono/node-server' import { Hono } from 'hono' const app = new Hono() app.get('/', (c) => c.text('Node + Hono')) serve({ fetch: app.fetch, port: 3000 })
Deno
import { Hono } from 'jsr:@hono/hono' const app = new Hono() app.get('/', (c) => c.text('Deno + Hono')) Deno.serve(app.fetch)
Vercel
// api/[[...route]].ts import { Hono } from 'hono' import { handle } from 'hono/vercel' const app = new Hono().basePath('/api') app.get('/hello', (c) => c.json({ msg: 'Hello from Vercel' })) export const GET = handle(app) export const POST = handle(app)
Pratik örnek: kimlik doğrulamalı ve veritabanlı REST API
import { Hono } from 'hono' import { jwt } from 'hono/jwt' import { logger } from 'hono/logger' import { cors } from 'hono/cors' import { zValidator } from '@hono/zod-validator' import { z } from 'zod' type Bindings = { DB: D1Database; JWT_SECRET: string } type Variables = { jwtPayload: { sub: string } } const app = new Hono<{ Bindings: Bindings; Variables: Variables }>() app.use('*', logger()) app.use('/api/*', cors({ origin: 'https://spinny.dev', credentials: true })) const auth = (c: any, next: any) => jwt({ secret: c.env.JWT_SECRET })(c, next) const api = app.basePath('/api') api.get('/posts', async (c) => { const { results } = await c.env.DB .prepare('SELECT id, title, created_at FROM posts ORDER BY created_at DESC LIMIT 50') .all() return c.json({ posts: results }) }) api.post( '/posts', auth, zValidator('json', z.object({ title: z.string().min(1).max(200), body: z.string().min(1), })), async (c) => { const { title, body } = c.req.valid('json') const userId = c.var.jwtPayload.sub const result = await c.env.DB .prepare('INSERT INTO posts (title, body, author_id) VALUES (?, ?, ?) RETURNING id') .bind(title, body, userId) .first<{ id: number }>() return c.json({ id: result?.id }, 201) } ) api.onError((err, c) => { console.error(err) return c.json({ error: 'Internal error' }, 500) }) export type AppType = typeof api export default app
Test
import { describe, it, expect } from 'vitest' import app from '../src/index' describe('GET /api/posts', () => { it('post listesini döner', async () => { const res = await app.request('/api/posts') expect(res.status).toBe(200) const body = await res.json() expect(body.posts).toBeInstanceOf(Array) }) })
En iyi uygulamalar
1. Route tanımlarını zincirleyin
const app = new Hono() .get('/posts', handler1) .post('/posts', handler2) .get('/posts/:id', handler3)
2. Uygulamayı değil, tipi export edin
İstemci AppType''i import etmeli.
3. Her domain için bir router
posts, users, webhooks için alt uygulamalar.
4. Sınırda her zaman doğrulayın
Her dış girdi zValidator''den geçmeli.
5. Global istemcilere değil binding''lere yaslanın
Cloudflare''de KV/D1/R2''ye c.env üzerinden erişin.
6. Router''ı optimize etmeden önce ölçün
Varsayılan SmartRouter vakaların %95''i için uygundur.
Sonuç
Hono 2026''da TypeScript ile edge''e hazır API''ler oluşturmanın fiili standardı haline geldi.
Başlangıç kontrol listesi:
npm create hono@latestve runtime şablonunuzu seçin- Route''ları zincirleyerek tanımlayın (
.get(...).post(...))logger,cors,secureHeaders''ı global middleware olarak ekleyin- Her girdiyi
@hono/zod-validatorile doğrulayınAppTypeexport edin ve API''yi tip güvenlihcistemcisiyle tüketin- HTTP sunucusu olmadan
app.request()ile testler yazınwrangler deploy(CF),vercel deployveya runtime''ınızın bundler''ı ile deploy edin