abdullah'ın Caka sayfasına dön
Hono.js: Hafif ve Hızlı Web Framework’ü ile Tanışın 🔥
Bu yazımda, son zamanlarda çok sevdiğim ve projelerimde aktif olarak kullandığım Hono.js framework’ünden bahsedeceğim. Yeni bir backend framework arayan veya edge computing ile ilgilenen arkadaşlar için güzel bir rehber olacağını düşünüyorum.
Keyifli okumalar 😊
Hono Nedir?
Kısaca söylemek gerekirse, Hono, JavaScript/TypeScript tarafında backend servisleri yazmamızı sağlayan bir web framework’ü.
Mesela bir kullanıcı kayıt/giriş sistemi düşünün: kullanıcı email ve şifresini giriyor, siz kontrol edip “giriş başarılı” ya da “şifre yanlış” dönüyorsunuz. İşte bu tarz backend işlemlerini Hono ile yazıyorsunuz.
İsmi Japonca’da “alev” anlamına geliyor (炎) 🔥 Ve gerçekten de çok hızlı. Web Standards üzerine inşa edilmiş, küçük ve basit bir framework.
Benim en çok hoşuma giden özelliği şu: aynı kodu Cloudflare Workers’da, Node.js’de, Deno’da, Bun’da… neredeyse her yerde çalıştırabiliyorsunuz. Yani bir kez yazıyorsunuz, istediğiniz platformda deploy ediyorsunuz.
Basit bir örnek vermek gerekirse:
import { Hono } from 'hono'
const app = new Hono()
app.get('/hi', (c) => c.text('Merhaba Hono!'))
export default appEvet, işte bu kadar. Sadece birkaç kodla “/hi” isteğini karşılayan ve çalışan bir API oluşturmuş olduk.
Hono’nun Öne Çıkan Özellikleri
1. Çok Hızlı ve Hafif
Hono’nun hono/tiny versiyonu sadece 14KB. Sıfır harici bağımlılık var ve Web Standard API'lerini kullanıyor. Serverless ortamlarda cold start süreleri için bu çok avantajlı.
2. Her Yerde Çalışıyor
Aynı kodu şu platformlarda çalıştırabilirsiniz:
Cloudflare Workers & Pages (bkz. Workers Nedir ?)
Node.js (klasik JavaScript runtime)
Deno (Node.js’in modern alternatifi)
Bun (yeni nesil, süper hızlı runtime)
AWS Lambda (Amazon’un serverless servisi)
ve daha fazlası…
3. TypeScript Desteği
Hono, TypeScript ile yazılmış ve tip güvenliğini ön planda tutuyor. Peki bu ne demek?
Diyelim ki bir endpoint’ten kullanıcı id’si alıyorsunuz. Bu id’nin string mi, number mı olduğunu bilmezseniz kodunuz beklenmedik hatalar verebilir. Hono’da TypeScript otomatik olarak bu tipi anlıyor ve size söylüyor. Yanlış bir şey yapmaya çalıştığınızda daha kodu çalıştırmadan editörünüzde hata görüyorsunuz.
app.get('/users/:id', (c) => {
const id = c.req.param('id') // TypeScript bunun string olduğunu biliyor
return c.json({ userId: id })
})Yani runtime’da “undefined is not a function” gibi hatalarla uğraşmak yerine, daha yazarken hatayı yakalıyorsunuz. Bu da hem geliştirme sürecini hızlandırıyor hem de daha güvenli kod yazmanızı sağlıyor.
4. Zengin Middleware Ekosistemi
Middleware’ler, her request’ten önce veya sonra çalışan kod parçaları. Mesela “bu endpoint’e sadece giriş yapmış kullanıcılar erişebilsin” gibi kontrolleri middleware ile yapıyorsunuz.
Hono’da CORS, JWT, Basic Auth, Logger, Compression gibi yaygın ihtiyaçlar için hazır middleware’ler geliyor. Yani bu özellikleri sıfırdan yazmak yerine, bir satırda projenize ekliyorsunuz.
Aslında çoğu framework’te middleware desteği var. Ama Hono’nun farkı şu: bu middleware’ler hem çok hafif hem de tüm platformlarda aynı şekilde çalışıyor. Ekstra bir ayar yapmanıza gerek kalmıyor.
Nezaman Hono Seçmeliyim ?
Edge/Serverless ortamlarında çalışacaksanız
Çoklu platform desteği gerekiyorsa (aynı kod Node, Deno, Bun, Cloudflare’de)
TypeScript-first geliştirme yapıyorsanız
Küçük bundle boyutu kritik önem taşıyorsa
Tabii her framework’ün kendine göre güçlü yanları var. Projenizin ihtiyacına göre seçim yapmanız en doğrusu.
Kurulum
Öncelikle yeni bir proje oluşturalım. Terminal’i açıyoruz ve şu komutu yazıyoruz:
npm create hono@latest my-appKarşımıza runtime seçenekleri çıkacak. Ben genellikle cloudflare-workers veya nodejs seçiyorum, siz projenize göre uygun olanı seçebilirsiniz.
Proje oluşturulduktan sonra klasöre girip bağımlılıkları yüklüyoruz:
cd my-app
npm installSonra geliştirme sunucusunu başlatıyoruz:
npm run devTarayıcıda http://localhost:8787 adresine gittiğimizde "Hello Hono!" mesajını görmemiz gerekiyor. 🎉
Temel Kullanım
Şimdi biraz kod yazalım. Basit bir CRUD API oluşturacağız:
import { Hono } from 'hono'
const app = new Hono()
// Ana sayfa
app.get('/', (c) => {
return c.text('API çalışıyor!')
})
// Kullanıcıları listele
app.get('/users', (c) => {
return c.json({
users: [
{ id: 1, name: 'Ali' },
{ id: 2, name: 'Ayşe' }
]
})
})
// Tek kullanıcı getir
app.get('/users/:id', (c) => {
const id = c.req.param('id')
return c.json({ userId: id, name: 'Ali' })
})
// Yeni kullanıcı oluştur
app.post('/users', async (c) => {
const body = await c.req.json()
return c.json({ message: 'Kullanıcı oluşturuldu', data: body }, 201)
})
export default appÖrneği bir veritabanı olmadan mock datalar üzerinden gösterdim. Gördüğünüz gibi Express’e çok benziyor. req ve res yerine tek bir c (context) objesi kullanıyoruz. Bu obje üzerinden hem request hem response işlemlerini yapabiliyoruz.
Ayrıca eğer Cloudflare Workers kullanıyorsanız, her request ile birlikte Cloudflare size otomatik olarak kullanıcının ülke/şehir bilgisi, koordinatları, zaman dilimi, posta kodu, internet sağlayıcısı ve AB ülkesi olup olmadığı (GDPR için faydalı) gibi bilgileri sunuyor. Ekstra API anahtarı yok, ek ücret yok, karmaşık entegrasyon yok. Bu bilgilere de c objesi üzerinden erişebiliyorsunuz.
Proje Yapısı Nasıl Olmalı?
Küçük projeler için tek dosya yeterli olabilir. Ama proje büyüdükçe düzenli bir yapıya ihtiyacımız oluyor.
Küçük Projeler
my-app/
├── src/
│ └── index.ts
├── package.json
└── tsconfig.jsonOrta/Büyük Projeler
Ben genellikle şöyle bir yapı kullanıyorum:
my-app/
├── src/
│ ├── index.ts # Entry point
│ ├── routes/
│ │ ├── users.ts # User route'ları
│ │ └── posts.ts # Post route'ları
│ ├── middleware/
│ │ └── auth.ts # Auth middleware
│ └── utils/
│ └── helpers.ts
├── package.json
└── tsconfig.jsonRoute’ları Ayırmak
Büyük projelerde route’ları ayrı dosyalara ayırmak çok önemli. Hono’da bunu app.route() ile yapıyoruz:
// src/routes/users.ts
import { Hono } from 'hono'
const users = new Hono()
users.get('/', (c) => c.json({ users: [] }))
users.post('/', (c) => c.json({ message: 'Oluşturuldu' }, 201))
users.get('/:id', (c) => c.json({ id: c.req.param('id') }))
export default users// src/index.ts
import { Hono } from 'hono'
import users from './routes/users'
import posts from './routes/posts'
const app = new Hono()
app.route('/api/users', users)
app.route('/api/posts', posts)
export default appBu şekilde her route dosyası kendi içinde bağımsız ve temiz kalıyor.
Middleware Kullanımı
Express’teki gibi Hono’da da middleware kullanabiliyoruz. Hatta built-in olarak birçok middleware geliyor:
import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { logger } from 'hono/logger'
const app = new Hono()
// Logger - tüm istekleri loglar
app.use(logger())
// CORS
app.use('/api/*', cors())
app.get('/', (c) => c.text('Merhaba!'))
export default appKendi Middleware’imizi Yazalım
Örneğin basit bir auth middleware:
import { HTTPException } from 'hono/http-exception'
const authMiddleware = async (c, next) => {
const token = c.req.header('Authorization')
if (!token) {
throw new HTTPException(401, { message: 'Token gerekli!' })
}
// Token doğrulama işlemleri...
await next()
}
// Kullanımı
app.use('/api/protected/*', authMiddleware)Bu kod ne yapıyor? /api/protected/ ile başlayan tüm endpoint'lere gelen isteklerde önce bu middleware çalışıyor. Request header'ında Authorization token'ı var mı diye bakıyor. Yoksa 401 hatası fırlatıp "Token gerekli!" diyor. Varsa next() ile asıl endpoint'e devam ediyor. Böylece tek bir yerde yazdığınız kontrol, o path altındaki tüm endpoint'leri korumuş oluyor.
Validation: Zod ile Beraber Kullanım
Benim en sevdiğim özelliklerden biri bu. Zod ile birlikte kullandığınızda hem validation hem de TypeScript tipleri otomatik oluyor.
Önce gerekli paketi yüklüyoruz:
npm install zod @hono/zod-validatorSonra şöyle kullanıyoruz:
import { Hono } from 'hono'
import { z } from 'zod'
import { zValidator } from '@hono/zod-validator'
const app = new Hono()
// Schema tanımlıyoruz
const createUserSchema = z.object({
name: z.string().min(2, 'İsim en az 2 karakter olmalı'),
email: z.string().email('Geçerli bir email girin'),
age: z.number().min(18).optional(),
})
// Route'a ekliyoruz
app.post(
'/users',
zValidator('json', createUserSchema),
(c) => {
const data = c.req.valid('json')
return c.json({ message: 'Kullanıcı oluşturuldu', user: data })
}
)Yanlış bir request geldiğinde otomatik olarak hata dönüyor. Aslında Java Spring gibi olgun backend framework’lerinde bu tarz validation işlemleri annotation’larla (@NotEmpty, @Email gibi) çok daha kolay ve yerleşik şekilde çözülüyor. Ama JavaScript/TypeScript dünyasında bu iş uzun süre biraz dağınıktı. Zod, bu boşluğu dolduran ve TypeScript ile mükemmel uyum sağlayan bir kütüphane. Hem tip güvenliği hem de runtime validation’ı tek bir yerde tanımlıyorsunuz. Hono ile birlikte kullanınca da oldukça pratik bir çözüm oluyor.
Hata Yönetimi
Hataları yönetmek için HTTPException ve app.onError kullanıyoruz:
import { HTTPException } from 'hono/http-exception'
// Hata fırlatmak
app.get('/users/:id', async (c) => {
const user = await findUser(c.req.param('id'))
if (!user) {
throw new HTTPException(404, { message: 'Kullanıcı bulunamadı' })
}
return c.json(user)
})
// Global hata yakalayıcı
app.onError((err, c) => {
console.error('Hata:', err)
if (err instanceof HTTPException) {
return c.json({ error: err.message }, err.status)
}
return c.json({ error: 'Sunucu hatası' }, 500)
})
// 404 handler
app.notFound((c) => {
return c.json({ error: 'Sayfa bulunamadı' }, 404)
})Bu kodda üç farklı hata yönetimi yaklaşımı var:
HTTPException: Endpoint içinde bir sorun olduğunda (kullanıcı bulunamadı, yetki yok, geçersiz istek gibi) bu exception'ı fırlatıyoruz. İlk parametre HTTP status kodu (404, 401, 400 gibi), ikinci parametre ise hata mesajı.
app.onError: Uygulamanın herhangi bir yerinde fırlatılan hataları yakalayan global bir handler. Tüm hataların tek bir noktadan geçmesini sağlıyor. Böylece her endpoint'te ayrı ayrı try-catch yazmak zorunda kalmıyorsunuz. Burada hatanın tipine göre farklı response'lar dönebiliyorsunuz.
app.notFound: Tanımlı olmayan bir endpoint'e istek geldiğinde çalışıyor. Mesela /api/asdfgh gibi var olmayan bir route'a istek atıldığında bu handler devreye giriyor ve düzgün bir 404 mesajı dönüyor.
RPC: Frontend ile Tip Paylaşımı
Bu özellik gerçekten çok güçlü ve bence Hono’nun en öne çıkan özelliklerinden biri.
Şöyle düşünün: normalde backend ve frontend ayrı projeler. Backend’de bir API endpoint’i yazıyorsunuz, frontend’de de o API’yi çağırıyorsunuz. Peki backend’de bir field ismini değiştirdiğinizde ne oluyor? Mesela userName'i username yaptınız. Frontend bunu bilmiyor, hala eski isimle çağırıyor ve runtime'da patlıyor. Kullanıcı şikayet edene kadar farkedemeyebilirsiniz bile.
Java Spring + React yazdığınızda da durum aynı. Backend ve frontend arasında bir “sözleşme” yok. Swagger/OpenAPI gibi araçlarla dokümantasyon oluşturabilirsiniz ama bu da manuel bir süreç ve güncel tutmak ayrı bir iş.
Hono’nun RPC özelliği bu sorunu köküten çözüyor. Backend’deki tipleri frontend’e otomatik olarak aktarıyorsunuz. API’nizde bir şey değiştirdiğinizde, frontend tarafında daha kodu çalıştırmadan, editörünüzde kırmızı hata görüyorsunuz. “Bu field artık yok” veya “bu parametre zorunlu oldu” gibi uyarıları anında alıyorsunuz.
Ama bir şart var: Backend ve frontend’in aynı repo’da (monorepo) olması gerekiyor. Çünkü frontend, backend’deki tipleri import ediyor. Ayrı repo’lardaysa bu özellik çalışmaz.
Backend Tarafı
// server.ts
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
const app = new Hono()
const route = app
.get('/users', (c) => {
return c.json({ users: [{ id: 1, name: 'Ali' }] })
})
.post(
'/users',
zValidator('json', z.object({
name: z.string(),
email: z.string().email(),
})),
(c) => {
const data = c.req.valid('json')
return c.json({ message: 'Oluşturuldu', user: data }, 201)
}
)
// Bu tipi export ediyoruz
export type AppType = typeof route
export default appFrontend Tarafı
// client.ts
import { hc } from 'hono/client'
import type { AppType } from './server'
const client = hc<AppType>('http://localhost:3000')
// Artık tüm endpoint'ler tip-güvenli.
const res = await client.users.$get()
const { users } = await res.json()
// users tipi otomatik olarak { id: number, name: string }[]
// POST isteği
await client.users.$post({
json: {
name: 'Ayşe',
email: 'ayse@example.com',
}
})Yanlış bir parametre göndermeye çalıştığınızda TypeScript anında hata veriyor. Aslında full stack bir type safety sağlanmış oluyor.
Sonuç
Bu yazıda Hono.js’in temel özelliklerini ve kullanımını anlattım. Özetlemek gerekirse:
Express’e benzer syntax, kolay geçiş
Çok hızlı ve hafif
Mükemmel TypeScript desteği
Her platformda çalışıyor
RPC ile frontend-backend tip paylaşımı
Zengin middleware ekosistemi
Eğer yeni bir proje başlıyorsanız veya Express’ten geçiş düşünüyorsanız, Hono’yu kesinlikle denemenizi öneririm.
Okuduğunuz için teşekkür ederim 🖐🏼😊