spinny:~/writing $ vim hono-framework-guide.md
1~2বছরের পর বছর JavaScript ওয়েব ফ্রেমওয়ার্ক বেছে নেওয়া মানে ছিল একটা ট্রেড-অফ মেনে নেওয়া: Express ইউনিভার্সাল কিন্তু ধীর এবং Node-নির্ভর, Fastify দ্রুত কিন্তু শুধু Node-এর জন্য, Next.js ফুল-ফিচার্ড কিন্তু ভারী। যখন এজ রানটাইম — Cloudflare Workers, Deno Deploy, Bun, Vercel Edge — এলো, এই ফ্রেমওয়ার্কগুলোর সীমাবদ্ধতা প্রকাশ পেলো: অসামঞ্জস্যপূর্ণ ডিপেন্ডেন্সি, বিশাল বান্ডেল, Node-এর `req`/`res`-এ আবদ্ধ API।3~4[Hono](https://hono.dev) (জাপানি ভাষায় "শিখা" 🔥) আধুনিক উত্তর। ১৪KB-এর কম একটা ফ্রেমওয়ার্ক, সম্পূর্ণরূপে ওয়েব স্ট্যান্ডার্ডের (`Request`, `Response`, `fetch`) উপর নির্মিত, যা যেকোনো JavaScript রানটাইমে চলে। একই কোড Cloudflare Workers, Bun, Deno, Node.js, Vercel, Netlify এবং AWS Lambda-তে — কোনো পরিবর্তন ছাড়াই — ডিপ্লয় হয়।5~6## কেন Hono7~8Hono তিনটি কাজ অন্য সবার চেয়ে ভালো করে:9~101. **পারফরম্যান্স।** `RegExpRouter` সমস্ত রুট প্যাটার্ন একটা regex-এ কম্পাইল করে, প্রচলিত রাউটারের লিনিয়ার লুপ এড়িয়ে যায়। বেঞ্চমার্ক প্রতি সেকেন্ডে ৪ লাখ ops ছাড়িয়ে যায়।112. **পোর্টেবিলিটি।** ওয়েব স্ট্যান্ডার্ড মানে শূন্য Node ডিপেন্ডেন্সি। একই `app.fetch` Cloudflare Worker-এ default export, `Bun.serve`-এ পাস, Deno সার্ভারে মাউন্ট, বা `@hono/node-server` দিয়ে অ্যাডাপ্ট হয়।123. **TypeScript-first DX।** লিটারাল টাইপ হিসেবে ইনফার করা পাথ প্যারামিটার, এন্ড-টু-এন্ড টাইপ-সেফ RPC ক্লায়েন্ট, ইনপুট-আউটপুট টাইপ ইনফার করা ভ্যালিডেটর।13~14```mermaid15graph LR16 Client[Client] -->|Request| App[app.fetch]17 App --> MW1[Middleware 1]18 MW1 --> MW2[Middleware 2]19 MW2 --> Router[RegExpRouter]20 Router --> Handler[Route Handler]21 Handler --> Context[c.json / c.text]22 Context -->|Response| Client23 App -.->|deploy| CF[Cloudflare Workers]24 App -.->|deploy| Bun[Bun]25 App -.->|deploy| Deno[Deno]26 App -.->|deploy| Node[Node.js]27 App -.->|deploy| Vercel[Vercel]28```29~30## শুরু করা31~32দ্রুততম উপায় হলো অফিসিয়াল স্টার্টার, যা আপনার বেছে নেওয়া রানটাইমের জন্য প্রজেক্ট স্ক্যাফোল্ড করে।33~34```bash35npm create hono@latest my-api36cd my-api37npm install38npm run dev39```40~41স্টার্টার জিজ্ঞেস করে কোন টেমপ্লেট: `cloudflare-workers`, `bun`, `deno`, `nodejs`, `vercel`, `aws-lambda`, `nextjs` ইত্যাদি। দ্রুত চেষ্টার জন্য একটা মাত্র ফাইল থেকেও শুরু করতে পারেন:42~43```typescript44// src/index.ts45import { Hono } from 'hono'46~47const app = new Hono()48~49app.get('/', (c) => c.text('Hello Hono!'))50~51export default app52```53~54Cloudflare Workers-এ এটাই যথেষ্ট। Bun-এ: `Bun.serve({ fetch: app.fetch, port: 3000 })`। Node-এ: `@hono/node-server` থেকে `serve({ fetch: app.fetch })`।55~56## রাউটিং57~58```typescript59import { Hono } from 'hono'60~61const app = new Hono()62~63app.get('/', (c) => c.text('Home'))64app.get('/posts/:id', (c) => {65 const id = c.req.param('id') // string, টাইপড66 return c.json({ id })67})68app.get('/posts/:id/comments/:commentId', (c) => {69 const { id, commentId } = c.req.param()70 return c.json({ id, commentId })71})72app.get('/files/*', (c) => c.text('Wildcard'))73app.post('/posts', async (c) => {74 const body = await c.req.json()75 return c.json({ created: body }, 201)76})77```78~79প্যারামিটারগুলো লিটারাল টাইপ হিসেবে ইনফার হয়: TypeScript জানে যে `c.req.param('id')` শুধু `string` রিটার্ন করে যদি আপনি প্যাটার্নে `:id` ডিক্লেয়ার করে থাকেন।80~81### রুট গ্রুপিং82~83```typescript84// routes/posts.ts85import { Hono } from 'hono'86~87const posts = new Hono()88posts.get('/', (c) => c.json({ posts: [] }))89posts.get('/:id', (c) => c.json({ id: c.req.param('id') }))90export default posts91~92// src/index.ts93import { Hono } from 'hono'94import posts from './routes/posts'95~96const app = new Hono()97app.route('/posts', posts)98```99~100## Context অবজেক্ট101~102```typescript103app.post('/echo', async (c) => {104 const userAgent = c.req.header('User-Agent')105 const page = c.req.query('page')106 const body = await c.req.json()107 const env = c.env108~109 c.set('requestId', crypto.randomUUID())110 const id = c.get('requestId')111~112 c.header('X-Request-Id', id)113 c.status(200)114 return c.json({ userAgent, page, body, id })115})116```117~118মূল রেসপন্স মেথডস: `c.text`, `c.json`, `c.html`, `c.body` (raw), `c.redirect`।119~120## মিডলওয়্যার: অনিয়ন মডেল121~122```typescript123import { Hono } from 'hono'124import { logger } from 'hono/logger'125import { cors } from 'hono/cors'126import { secureHeaders } from 'hono/secure-headers'127~128const app = new Hono()129~130app.use('*', logger())131app.use('*', secureHeaders())132app.use('/api/*', cors({ origin: 'https://spinny.dev' }))133~134app.use('*', async (c, next) => {135 const start = performance.now()136 await next()137 c.header('X-Response-Time', `${performance.now() - start}ms`)138})139```140~141`await next()` কন্ট্রোল পরবর্তী মিডলওয়্যারে দেয়।142~143### বিল্ট-ইন মিডলওয়্যার144~145| মিডলওয়্যার | কাজ |146|-----------|---------|147| `logger` | মেথড, পাথ, স্ট্যাটাস, ডিউরেশনের স্ট্রাকচার্ড লগ |148| `cors` | origin, মেথড, হেডার দিয়ে কনফিগারেবল CORS |149| `csrf` | origin-ভিত্তিক CSRF সুরক্ষা |150| `secureHeaders` | CSP, HSTS, X-Frame-Options সেট করে |151| `bearerAuth` / `basicAuth` | আউট-অফ-দ্য-বক্স Bearer/Basic auth |152| `jwt` | `jose` দিয়ে JWT verify/sign |153| `etag` | ETag জেনারেট করে, 304 হ্যান্ডল করে |154| `cache` | Web Cache API ক্যাশিং |155| `compress` | রেসপন্সে gzip/deflate |156| `bodyLimit` | থ্রেশহোল্ডের উপরের body বাতিল |157| `timing` | প্রোফাইলিংয়ের জন্য Server-Timing হেডার |158~159### টাইপ-সেফ কাস্টম মিডলওয়্যার160~161```typescript162import { createMiddleware } from 'hono/factory'163~164type AuthVars = { userId: string; role: 'user' | 'admin' }165~166export const requireAuth = createMiddleware<{ Variables: AuthVars }>(167 async (c, next) => {168 const token = c.req.header('Authorization')?.replace('Bearer ', '')169 if (!token) return c.json({ error: 'Unauthorized' }, 401)170~171 const payload = await verifyJwt(token)172 c.set('userId', payload.sub)173 c.set('role', payload.role)174 await next()175 }176)177~178app.get('/me', requireAuth, (c) => {179 const userId = c.var.userId180 return c.json({ userId })181})182```183~184## Zod দিয়ে ভ্যালিডেশন185~186```typescript187import { Hono } from 'hono'188import { zValidator } from '@hono/zod-validator'189import { z } from 'zod'190~191const createPost = z.object({192 title: z.string().min(1).max(200),193 body: z.string().min(1),194 tags: z.array(z.string()).default([]),195})196~197app.post(198 '/posts',199 zValidator('json', createPost),200 (c) => {201 const data = c.req.valid('json')202 return c.json({ ok: true, post: data }, 201)203 }204)205```206~207## RPC: এন্ড-টু-এন্ড টাইপ-সেফ ক্লায়েন্ট208~209```typescript210// server.ts211import { Hono } from 'hono'212import { zValidator } from '@hono/zod-validator'213import { z } from 'zod'214~215const app = new Hono()216 .get('/posts/:id', (c) =>217 c.json({ id: c.req.param('id'), title: 'Hello' })218 )219 .post(220 '/posts',221 zValidator('json', z.object({ title: z.string(), body: z.string() })),222 (c) => c.json({ ok: true }, 201)223 )224~225export type AppType = typeof app226export default app227```228~229```typescript230// client.ts231import { hc } from 'hono/client'232import type { AppType } from './server'233~234const client = hc<AppType>('https://api.spinny.dev')235~236const res = await client.posts[':id'].$get({ param: { id: '42' } })237if (res.ok) {238 const data = await res.json()239 console.log(data.title)240}241~242const created = await client.posts.$post({243 json: { title: 'হ্যালো', body: 'Hono একটা শিখা' },244})245```246~247### স্ট্যাটাস কোড ডিসক্রিমিনেশন248~249```typescript250.get('/posts/:id', (c) => {251 const post = findPost(c.req.param('id'))252 if (!post) return c.json({ error: 'not found' }, 404)253 return c.json({ post }, 200)254})255```256~257```typescript258const res = await client.posts[':id'].$get({ param: { id } })259if (res.status === 404) {260 const { error } = await res.json()261}262if (res.status === 200) {263 const { post } = await res.json()264}265```266~267## রাউটার এবং পারফরম্যান্স268~269| রাউটার | শক্তি | কখন ব্যবহার |270|--------|-----------|-------------|271| `RegExpRouter` | সর্বোচ্চ গতি, কম্পাইল্ড regex | বেশিরভাগ API-এর ডিফল্ট |272| `TrieRouter` | সব প্যাটার্ন সাপোর্ট | RegExp হ্যান্ডল না করা জটিল প্যাটার্ন |273| `SmartRouter` | অটো সেরাটা বেছে নেয় | প্রস্তাবিত ডিফল্ট |274| `LinearRouter` | অতি-দ্রুত রেজিস্ট্রেশন | ওয়ান-শট ওয়ার্কার, ক্রিটিকাল কোল্ড স্টার্ট |275| `PatternRouter` | ন্যূনতম বান্ডেল (<15KB) | কড়া সাইজ সীমাবদ্ধতা |276~277```typescript278import { Hono } from 'hono'279import { LinearRouter } from 'hono/router/linear-router'280~281const app = new Hono({ router: new LinearRouter() })282```283~284## মাল্টি-রানটাইম ডিপ্লয়মেন্ট285~286### Cloudflare Workers287~288```typescript289import { Hono } from 'hono'290~291type Bindings = { MY_KV: KVNamespace; DB: D1Database }292const app = new Hono<{ Bindings: Bindings }>()293~294app.get('/cache/:key', async (c) => {295 const value = await c.env.MY_KV.get(c.req.param('key'))296 return c.json({ value })297})298~299export default app300```301~302ডিপ্লয়: `npx wrangler deploy`।303~304### Bun305~306```typescript307import { Hono } from 'hono'308const app = new Hono()309app.get('/', (c) => c.text('Bun + Hono'))310~311Bun.serve({ fetch: app.fetch, port: 3000 })312```313~314### Node.js315~316```typescript317import { serve } from '@hono/node-server'318import { Hono } from 'hono'319~320const app = new Hono()321app.get('/', (c) => c.text('Node + Hono'))322~323serve({ fetch: app.fetch, port: 3000 })324```325~326### Deno327~328```typescript329import { Hono } from 'jsr:@hono/hono'330const app = new Hono()331app.get('/', (c) => c.text('Deno + Hono'))332Deno.serve(app.fetch)333```334~335### Vercel336~337```typescript338// api/[[...route]].ts339import { Hono } from 'hono'340import { handle } from 'hono/vercel'341~342const app = new Hono().basePath('/api')343app.get('/hello', (c) => c.json({ msg: 'Hello from Vercel' }))344~345export const GET = handle(app)346export const POST = handle(app)347```348~349## ব্যবহারিক উদাহরণ: auth এবং DB সহ REST API350~351```typescript352import { Hono } from 'hono'353import { jwt } from 'hono/jwt'354import { logger } from 'hono/logger'355import { cors } from 'hono/cors'356import { zValidator } from '@hono/zod-validator'357import { z } from 'zod'358~359type Bindings = { DB: D1Database; JWT_SECRET: string }360type Variables = { jwtPayload: { sub: string } }361~362const app = new Hono<{ Bindings: Bindings; Variables: Variables }>()363~364app.use('*', logger())365app.use('/api/*', cors({ origin: 'https://spinny.dev', credentials: true }))366~367const auth = (c: any, next: any) =>368 jwt({ secret: c.env.JWT_SECRET })(c, next)369~370const api = app.basePath('/api')371~372api.get('/posts', async (c) => {373 const { results } = await c.env.DB374 .prepare('SELECT id, title, created_at FROM posts ORDER BY created_at DESC LIMIT 50')375 .all()376 return c.json({ posts: results })377})378~379api.post(380 '/posts',381 auth,382 zValidator('json', z.object({383 title: z.string().min(1).max(200),384 body: z.string().min(1),385 })),386 async (c) => {387 const { title, body } = c.req.valid('json')388 const userId = c.var.jwtPayload.sub389 const result = await c.env.DB390 .prepare('INSERT INTO posts (title, body, author_id) VALUES (?, ?, ?) RETURNING id')391 .bind(title, body, userId)392 .first<{ id: number }>()393 return c.json({ id: result?.id }, 201)394 }395)396~397api.onError((err, c) => {398 console.error(err)399 return c.json({ error: 'Internal error' }, 500)400})401~402export type AppType = typeof api403export default app404```405~406## টেস্টিং407~408```typescript409import { describe, it, expect } from 'vitest'410import app from '../src/index'411~412describe('GET /api/posts', () => {413 it('পোস্টের তালিকা ফেরত দেয়', async () => {414 const res = await app.request('/api/posts')415 expect(res.status).toBe(200)416 const body = await res.json()417 expect(body.posts).toBeInstanceOf(Array)418 })419})420```421~422## বেস্ট প্র্যাকটিস423~424### 1. রুট ডেফিনিশন চেইন করুন425~426```typescript427const app = new Hono()428 .get('/posts', handler1)429 .post('/posts', handler2)430 .get('/posts/:id', handler3)431```432~433### 2. ইমপ্লিমেন্টেশন না, টাইপ এক্সপোর্ট করুন434~435ক্লায়েন্ট অবশ্যই `AppType` ইম্পোর্ট করবে।436~437### 3. প্রতি ডোমেইনে একটা রাউটার438~439`posts`, `users`, `webhooks`-এর জন্য আলাদা সাব-অ্যাপ। `app.route()` দিয়ে কম্পোজ করুন।440~441### 4. সবসময় বর্ডারে ভ্যালিডেট করুন442~443প্রতিটা এক্সটার্নাল ইনপুট `zValidator`-এর মধ্য দিয়ে যাবে।444~445### 5. গ্লোবাল ক্লায়েন্ট না, বাইন্ডিংয়ের উপর নির্ভর করুন446~447Cloudflare-এ `c.env`-এর মাধ্যমে KV/D1/R2 অ্যাক্সেস করুন।448~449### 6. রাউটার অপটিমাইজ করার আগে মাপুন450~451ডিফল্ট `SmartRouter` ৯৫% ক্ষেত্রে ঠিক।452~453## উপসংহার454~455Hono ২০২৬ সালে TypeScript-এ এজ-রেডি API তৈরির ডি ফ্যাক্টো স্ট্যান্ডার্ড হয়ে উঠেছে। ওয়েব স্ট্যান্ডার্ড, পারফরম্যান্স, টাইপ সেফটি এবং পোর্টেবিলিটির সংমিশ্রণ ঠিক সেই সমস্যাগুলোই সমাধান করে যা প্রচলিত ফ্রেমওয়ার্ককে বাধা দিচ্ছিল।456~457> **শুরু করার চেকলিস্ট:**458>459> - [x] `npm create hono@latest` এবং রানটাইম টেমপ্লেট বাছুন460> - [x] চেইনিং দিয়ে রুট ডিফাইন করুন (`.get(...).post(...)`)461> - [x] `logger`, `cors`, `secureHeaders` গ্লোবাল মিডলওয়্যার হিসেবে যোগ করুন462> - [x] প্রতিটা ইনপুট `@hono/zod-validator` দিয়ে ভ্যালিডেট করুন463> - [x] `AppType` এক্সপোর্ট করুন এবং টাইপ-সেফ `hc` ক্লায়েন্ট দিয়ে API ব্যবহার করুন464> - [x] HTTP সার্ভার ছাড়া `app.request()` দিয়ে টেস্ট লিখুন465> - [x] `wrangler deploy` (CF), `vercel deploy` বা আপনার রানটাইমের বান্ডলার দিয়ে ডিপ্লয় করুন466~
NORMAL · hono-framework-guide.md [readonly]466 lines · :q to close