বছরের পর বছর JavaScript ওয়েব ফ্রেমওয়ার্ক বেছে নেওয়া মানে ছিল একটা ট্রেড-অফ মেনে নেওয়া: Express ইউনিভার্সাল কিন্তু ধীর এবং Node-নির্ভর, Fastify দ্রুত কিন্তু শুধু Node-এর জন্য, Next.js ফুল-ফিচার্ড কিন্তু ভারী। যখন এজ রানটাইম — Cloudflare Workers, Deno Deploy, Bun, Vercel Edge — এলো, এই ফ্রেমওয়ার্কগুলোর সীমাবদ্ধতা প্রকাশ পেলো: অসামঞ্জস্যপূর্ণ ডিপেন্ডেন্সি, বিশাল বান্ডেল, Node-এর req/res-এ আবদ্ধ API।
Hono (জাপানি ভাষায় "শিখা" 🔥) আধুনিক উত্তর। ১৪KB-এর কম একটা ফ্রেমওয়ার্ক, সম্পূর্ণরূপে ওয়েব স্ট্যান্ডার্ডের (Request, Response, fetch) উপর নির্মিত, যা যেকোনো JavaScript রানটাইমে চলে। একই কোড Cloudflare Workers, Bun, Deno, Node.js, Vercel, Netlify এবং AWS Lambda-তে — কোনো পরিবর্তন ছাড়াই — ডিপ্লয় হয়।
কেন Hono
Hono তিনটি কাজ অন্য সবার চেয়ে ভালো করে:
- পারফরম্যান্স।
RegExpRouterসমস্ত রুট প্যাটার্ন একটা regex-এ কম্পাইল করে, প্রচলিত রাউটারের লিনিয়ার লুপ এড়িয়ে যায়। বেঞ্চমার্ক প্রতি সেকেন্ডে ৪ লাখ ops ছাড়িয়ে যায়। - পোর্টেবিলিটি। ওয়েব স্ট্যান্ডার্ড মানে শূন্য Node ডিপেন্ডেন্সি। একই
app.fetchCloudflare Worker-এ default export,Bun.serve-এ পাস, Deno সার্ভারে মাউন্ট, বা@hono/node-serverদিয়ে অ্যাডাপ্ট হয়। - TypeScript-first DX। লিটারাল টাইপ হিসেবে ইনফার করা পাথ প্যারামিটার, এন্ড-টু-এন্ড টাইপ-সেফ RPC ক্লায়েন্ট, ইনপুট-আউটপুট টাইপ ইনফার করা ভ্যালিডেটর।
শুরু করা
দ্রুততম উপায় হলো অফিসিয়াল স্টার্টার, যা আপনার বেছে নেওয়া রানটাইমের জন্য প্রজেক্ট স্ক্যাফোল্ড করে।
npm create hono@latest my-api cd my-api npm install npm run dev
স্টার্টার জিজ্ঞেস করে কোন টেমপ্লেট: cloudflare-workers, bun, deno, nodejs, vercel, aws-lambda, nextjs ইত্যাদি। দ্রুত চেষ্টার জন্য একটা মাত্র ফাইল থেকেও শুরু করতে পারেন:
// src/index.ts import { Hono } from 'hono' const app = new Hono() app.get('/', (c) => c.text('Hello Hono!')) export default app
Cloudflare Workers-এ এটাই যথেষ্ট। Bun-এ: Bun.serve({ fetch: app.fetch, port: 3000 })। Node-এ: @hono/node-server থেকে serve({ fetch: app.fetch })।
রাউটিং
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, টাইপড 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) })
প্যারামিটারগুলো লিটারাল টাইপ হিসেবে ইনফার হয়: TypeScript জানে যে c.req.param('id') শুধু string রিটার্ন করে যদি আপনি প্যাটার্নে :id ডিক্লেয়ার করে থাকেন।
রুট গ্রুপিং
// 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 অবজেক্ট
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 }) })
মূল রেসপন্স মেথডস: c.text, c.json, c.html, c.body (raw), c.redirect।
মিডলওয়্যার: অনিয়ন মডেল
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`) })
await next() কন্ট্রোল পরবর্তী মিডলওয়্যারে দেয়।
বিল্ট-ইন মিডলওয়্যার
| মিডলওয়্যার | কাজ |
|---|---|
logger | মেথড, পাথ, স্ট্যাটাস, ডিউরেশনের স্ট্রাকচার্ড লগ |
cors | origin, মেথড, হেডার দিয়ে কনফিগারেবল CORS |
csrf | origin-ভিত্তিক CSRF সুরক্ষা |
secureHeaders | CSP, HSTS, X-Frame-Options সেট করে |
bearerAuth / basicAuth | আউট-অফ-দ্য-বক্স Bearer/Basic auth |
jwt | jose দিয়ে JWT verify/sign |
etag | ETag জেনারেট করে, 304 হ্যান্ডল করে |
cache | Web Cache API ক্যাশিং |
compress | রেসপন্সে gzip/deflate |
bodyLimit | থ্রেশহোল্ডের উপরের body বাতিল |
timing | প্রোফাইলিংয়ের জন্য Server-Timing হেডার |
টাইপ-সেফ কাস্টম মিডলওয়্যার
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 দিয়ে ভ্যালিডেশন
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: এন্ড-টু-এন্ড টাইপ-সেফ ক্লায়েন্ট
// 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: 'হ্যালো', body: 'Hono একটা শিখা' }, })
স্ট্যাটাস কোড ডিসক্রিমিনেশন
.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() }
রাউটার এবং পারফরম্যান্স
| রাউটার | শক্তি | কখন ব্যবহার |
|---|---|---|
RegExpRouter | সর্বোচ্চ গতি, কম্পাইল্ড regex | বেশিরভাগ API-এর ডিফল্ট |
TrieRouter | সব প্যাটার্ন সাপোর্ট | RegExp হ্যান্ডল না করা জটিল প্যাটার্ন |
SmartRouter | অটো সেরাটা বেছে নেয় | প্রস্তাবিত ডিফল্ট |
LinearRouter | অতি-দ্রুত রেজিস্ট্রেশন | ওয়ান-শট ওয়ার্কার, ক্রিটিকাল কোল্ড স্টার্ট |
PatternRouter | ন্যূনতম বান্ডেল (<15KB) | কড়া সাইজ সীমাবদ্ধতা |
import { Hono } from 'hono' import { LinearRouter } from 'hono/router/linear-router' const app = new Hono({ router: new LinearRouter() })
মাল্টি-রানটাইম ডিপ্লয়মেন্ট
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
ডিপ্লয়: 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)
ব্যবহারিক উদাহরণ: auth এবং DB সহ 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
টেস্টিং
import { describe, it, expect } from 'vitest' import app from '../src/index' describe('GET /api/posts', () => { it('পোস্টের তালিকা ফেরত দেয়', async () => { const res = await app.request('/api/posts') expect(res.status).toBe(200) const body = await res.json() expect(body.posts).toBeInstanceOf(Array) }) })
বেস্ট প্র্যাকটিস
1. রুট ডেফিনিশন চেইন করুন
const app = new Hono() .get('/posts', handler1) .post('/posts', handler2) .get('/posts/:id', handler3)
2. ইমপ্লিমেন্টেশন না, টাইপ এক্সপোর্ট করুন
ক্লায়েন্ট অবশ্যই AppType ইম্পোর্ট করবে।
3. প্রতি ডোমেইনে একটা রাউটার
posts, users, webhooks-এর জন্য আলাদা সাব-অ্যাপ। app.route() দিয়ে কম্পোজ করুন।
4. সবসময় বর্ডারে ভ্যালিডেট করুন
প্রতিটা এক্সটার্নাল ইনপুট zValidator-এর মধ্য দিয়ে যাবে।
5. গ্লোবাল ক্লায়েন্ট না, বাইন্ডিংয়ের উপর নির্ভর করুন
Cloudflare-এ c.env-এর মাধ্যমে KV/D1/R2 অ্যাক্সেস করুন।
6. রাউটার অপটিমাইজ করার আগে মাপুন
ডিফল্ট SmartRouter ৯৫% ক্ষেত্রে ঠিক।
উপসংহার
Hono ২০২৬ সালে TypeScript-এ এজ-রেডি API তৈরির ডি ফ্যাক্টো স্ট্যান্ডার্ড হয়ে উঠেছে। ওয়েব স্ট্যান্ডার্ড, পারফরম্যান্স, টাইপ সেফটি এবং পোর্টেবিলিটির সংমিশ্রণ ঠিক সেই সমস্যাগুলোই সমাধান করে যা প্রচলিত ফ্রেমওয়ার্ককে বাধা দিচ্ছিল।
শুরু করার চেকলিস্ট:
npm create hono@latestএবং রানটাইম টেমপ্লেট বাছুন- চেইনিং দিয়ে রুট ডিফাইন করুন (
.get(...).post(...))logger,cors,secureHeadersগ্লোবাল মিডলওয়্যার হিসেবে যোগ করুন- প্রতিটা ইনপুট
@hono/zod-validatorদিয়ে ভ্যালিডেট করুনAppTypeএক্সপোর্ট করুন এবং টাইপ-সেফhcক্লায়েন্ট দিয়ে API ব্যবহার করুন- HTTP সার্ভার ছাড়া
app.request()দিয়ে টেস্ট লিখুনwrangler deploy(CF),vercel deployবা আপনার রানটাইমের বান্ডলার দিয়ে ডিপ্লয় করুন