spinny:~/writing $ less hono-framework-guide.md
12لسنوات، كان اختيار إطار ويب JavaScript يعني قبول مقايضة: Express عام لكنه بطيء ومرتبط بـ Node، و Fastify سريع لكنه فقط لـ Node، و Next.js كامل لكنه ثقيل. عندما ظهرت بيئات تشغيل الحافة — Cloudflare Workers و Deno Deploy و Bun و Vercel Edge — أظهرت تلك الأطر حدودها: تبعيات غير متوافقة، حزم ضخمة، APIs مرتبطة بـ `req`/`res` الخاصة بـ Node.34[Hono](https://hono.dev) (تعني "اللهب" 🔥 بالـيابانية) هي الإجابة الحديثة. إطار يقل حجمه عن 14KB، مبني بالكامل على معايير الويب (`Request`، `Response`، `fetch`)، ويعمل في أي مكان توجد فيه بيئة تشغيل JavaScript. نفس الكود يُنشر على Cloudflare Workers و Bun و Deno و Node.js و Vercel و Netlify و AWS Lambda — دون تغيير.56## لماذا Hono78يقوم Hono بثلاثة أشياء أفضل من أي شخص آخر:9101. **الأداء.** يقوم `RegExpRouter` بتجميع كل أنماط التوجيه في تعبير منتظم واحد، متجنباً الحلقات الخطية للموجهات التقليدية. تتجاوز المعايير 400000 عملية في الثانية، مما يضع Hono ضمن أسرع الموجهات في نظام JavaScript البيئي.112. **قابلية النقل.** معايير الويب تعني صفر تبعيات على Node. نفس `app.fetch` يُصدَّر افتراضياً في Cloudflare Worker، ويُمرَّر إلى `Bun.serve`، ويُحمَّل في خادم Deno، أو يُكيَّف مع `@hono/node-server`.123. **DX يعتمد TypeScript أولاً.** معاملات المسار مستنتجة كأنواع حرفية، عميل RPC آمن نوعياً من طرف لطرف، مدققات تستنتج أنواع المدخلات والمخرجات.1314```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```2930## البدء3132أسرع طريقة هي المُهيِّئ الرسمي، الذي ينشئ المشروع لبيئة التشغيل المختارة.3334```bash35npm create hono@latest my-api36cd my-api37npm install38npm run dev39```4041يسأل المُهيِّئ عن أي قالب: `cloudflare-workers`، `bun`، `deno`، `nodejs`، `vercel`، `aws-lambda`، `nextjs` وغيرها. للتجربة السريعة، يمكنك أيضاً البدء من ملف واحد:4243```typescript44// src/index.ts45import { Hono } from 'hono'4647const app = new Hono()4849app.get('/', (c) => c.text('Hello Hono!'))5051export default app52```5354على Cloudflare Workers هذا يكفي. على Bun: `Bun.serve({ fetch: app.fetch, port: 3000 })`. على Node: `serve({ fetch: app.fetch })` من `@hono/node-server`. السطح متطابق.5556## التوجيه5758تُعلَن المسارات بطرق فعل HTTP وتدعم المعاملات والأحرف البديلة والتعبيرات المنتظمة.5960```typescript61import { Hono } from 'hono'6263const app = new Hono()6465app.get('/', (c) => c.text('Home'))66app.get('/posts/:id', (c) => {67 const id = c.req.param('id') // string، مكتوب نوعياً68 return c.json({ id })69})70app.get('/posts/:id/comments/:commentId', (c) => {71 const { id, commentId } = c.req.param()72 return c.json({ id, commentId })73})74app.get('/files/*', (c) => c.text('Wildcard'))75app.post('/posts', async (c) => {76 const body = await c.req.json()77 return c.json({ created: body }, 201)78})79```8081تُستنتج المعاملات كأنواع حرفية: يعرف TypeScript أن `c.req.param('id')` يُعيد `string` فقط إذا أعلنت `:id` في النمط. الخطأ في التهجئة هو خطأ وقت التجميع.8283### تجميع المسارات8485`app.route()` يسمح بتركيب التطبيقات الفرعية كوحدات، كل واحدة بادئتها الخاصة.8687```typescript88// routes/posts.ts89import { Hono } from 'hono'9091const posts = new Hono()92posts.get('/', (c) => c.json({ posts: [] }))93posts.get('/:id', (c) => c.json({ id: c.req.param('id') }))94export default posts9596// src/index.ts97import { Hono } from 'hono'98import posts from './routes/posts'99100const app = new Hono()101app.route('/posts', posts)102```103104ترث المسارات المتداخلة المسار الأساسي ونوع تطبيق الجذر، لذلك يرى عميل RPC البنية كاملة.105106## كائن Context107108كل معالج يستقبل `c` — Context للطلب الحالي. هو الـ API الوحيد الذي تحتاج تعلمه لقراءة المدخلات وإنتاج المخرجات.109110```typescript111app.post('/echo', async (c) => {112 // قراءة113 const userAgent = c.req.header('User-Agent')114 const page = c.req.query('page')115 const body = await c.req.json()116 const env = c.env // الروابط (KV، D1، الأسرار على Cloudflare)117118 // متغيرات مشتركة بين الوسيط والمعالج119 c.set('requestId', crypto.randomUUID())120 const id = c.get('requestId')121122 // الاستجابة123 c.header('X-Request-Id', id)124 c.status(200)125 return c.json({ userAgent, page, body, id })126})127```128129طرق الاستجابة الرئيسية هي `c.text` و `c.json` و `c.html` و `c.body` (raw) و `c.redirect`. كلها تقبل رمز حالة كوسيطة ثانية.130131## الوسطاء: نموذج البصلة132133الوسطاء هي دوال `(c, next) => ...` يمكنها تشغيل كود قبل المعالج وبعده. عند تركيبها تشكل نموذج البصلة الكلاسيكي: أول وسيط مسجل هو أول من يبدأ وآخر من ينتهي.134135```typescript136import { Hono } from 'hono'137import { logger } from 'hono/logger'138import { cors } from 'hono/cors'139import { secureHeaders } from 'hono/secure-headers'140141const app = new Hono()142143app.use('*', logger())144app.use('*', secureHeaders())145app.use('/api/*', cors({ origin: 'https://spinny.dev' }))146147app.use('*', async (c, next) => {148 const start = performance.now()149 await next()150 c.header('X-Response-Time', `${performance.now() - start}ms`)151})152```153154`await next()` يُسلِّم التحكم للوسيط التالي. كل ما بعده يعمل في مرحلة الاستجابة، بعد أن ينتج المعالج نتيجة. إعادة `Response` قبل `next()` يقصر السلسلة.155156### الوسطاء المدمجون157158| الوسيط | الغرض |159|-----------|---------|160| `logger` | سجلات منظمة للطريقة، المسار، الحالة، المدة |161| `cors` | CORS قابل للتكوين بحسب الأصل، الطرق، الترويسات |162| `csrf` | حماية CSRF قائمة على الأصل |163| `secureHeaders` | يضبط CSP، HSTS، X-Frame-Options |164| `bearerAuth` / `basicAuth` | مصادقة Bearer/Basic جاهزة |165| `jwt` | تحقق وتوقيع JWT بـ `jose` |166| `etag` | يولد ETag ويعالج 304 |167| `cache` | تخزين مؤقت عبر Web Cache API |168| `compress` | gzip/deflate على الاستجابة |169| `bodyLimit` | يرفض الأجسام فوق الحد |170| `timing` | ترويسة Server-Timing للتحليل |171172### وسطاء مخصصون آمنون نوعياً173174```typescript175import { createMiddleware } from 'hono/factory'176177type AuthVars = { userId: string; role: 'user' | 'admin' }178179export const requireAuth = createMiddleware<{ Variables: AuthVars }>(180 async (c, next) => {181 const token = c.req.header('Authorization')?.replace('Bearer ', '')182 if (!token) return c.json({ error: 'Unauthorized' }, 401)183184 const payload = await verifyJwt(token)185 c.set('userId', payload.sub)186 c.set('role', payload.role)187 await next()188 }189)190191app.get('/me', requireAuth, (c) => {192 const userId = c.var.userId // string، مكتوب نوعياً193 return c.json({ userId })194})195```196197## التحقق بـ Zod198199```typescript200import { Hono } from 'hono'201import { zValidator } from '@hono/zod-validator'202import { z } from 'zod'203204const createPost = z.object({205 title: z.string().min(1).max(200),206 body: z.string().min(1),207 tags: z.array(z.string()).default([]),208})209210app.post(211 '/posts',212 zValidator('json', createPost),213 (c) => {214 const data = c.req.valid('json')215 return c.json({ ok: true, post: data }, 201)216 }217)218```219220إذا فشل الجسم في التحقق، يستجيب Hono بـ 400 مع خطأ Zod قبل حتى استدعاء المعالج. يمكن أيضاً التحقق من `query` و `param` و `header` و `cookie` و `form`.221222## RPC: عميل آمن نوعياً من طرف لطرف223224```typescript225// server.ts226import { Hono } from 'hono'227import { zValidator } from '@hono/zod-validator'228import { z } from 'zod'229230const app = new Hono()231 .get('/posts/:id', (c) =>232 c.json({ id: c.req.param('id'), title: 'Hello' })233 )234 .post(235 '/posts',236 zValidator('json', z.object({ title: z.string(), body: z.string() })),237 (c) => c.json({ ok: true }, 201)238 )239240export type AppType = typeof app241export default app242```243244```typescript245// client.ts246import { hc } from 'hono/client'247import type { AppType } from './server'248249const client = hc<AppType>('https://api.spinny.dev')250251const res = await client.posts[':id'].$get({ param: { id: '42' } })252if (res.ok) {253 const data = await res.json()254 console.log(data.title)255}256257const created = await client.posts.$post({258 json: { title: 'مرحبا', body: 'Hono لهب' },259})260```261262أعد تسمية مسار على الخادم وسيُكسر TypeScript الخاص بالعميل فوراً في CI. نفس ميزة tRPC، لكن عبر HTTP المعياري، دون وسطاء محددين، ومع حزمة صغيرة جداً.263264### تمييز رموز الحالة265266```typescript267.get('/posts/:id', (c) => {268 const post = findPost(c.req.param('id'))269 if (!post) return c.json({ error: 'not found' }, 404)270 return c.json({ post }, 200)271})272```273274```typescript275const res = await client.posts[':id'].$get({ param: { id } })276if (res.status === 404) {277 const { error } = await res.json() // { error: string }278}279if (res.status === 200) {280 const { post } = await res.json() // { post: Post }281}282```283284## الموجهات والأداء285286| الموجه | نقاط القوة | متى يستخدم |287|--------|-----------|-------------|288| `RegExpRouter` | أعلى سرعة، regex مجمع | افتراضي لمعظم APIs |289| `TrieRouter` | يدعم كل الأنماط | الأنماط المعقدة التي لا يتعامل معها RegExp |290| `SmartRouter` | يختار الأفضل تلقائياً | الافتراضي الموصى به |291| `LinearRouter` | تسجيل فائق السرعة | عمال one-shot، البدء البارد حرج |292| `PatternRouter` | حزمة صغرى (<15KB) | قيود حجم متطرفة |293294```typescript295import { Hono } from 'hono'296import { LinearRouter } from 'hono/router/linear-router'297298const app = new Hono({ router: new LinearRouter() })299```300301## النشر متعدد البيئات302303### Cloudflare Workers304305```typescript306import { Hono } from 'hono'307308type Bindings = { MY_KV: KVNamespace; DB: D1Database }309const app = new Hono<{ Bindings: Bindings }>()310311app.get('/cache/:key', async (c) => {312 const value = await c.env.MY_KV.get(c.req.param('key'))313 return c.json({ value })314})315316export default app317```318319نشر: `npx wrangler deploy`.320321### Bun322323```typescript324import { Hono } from 'hono'325const app = new Hono()326app.get('/', (c) => c.text('Bun + Hono'))327328Bun.serve({ fetch: app.fetch, port: 3000 })329```330331### Node.js332333```typescript334import { serve } from '@hono/node-server'335import { Hono } from 'hono'336337const app = new Hono()338app.get('/', (c) => c.text('Node + Hono'))339340serve({ fetch: app.fetch, port: 3000 })341```342343### Deno344345```typescript346import { Hono } from 'jsr:@hono/hono'347const app = new Hono()348app.get('/', (c) => c.text('Deno + Hono'))349Deno.serve(app.fetch)350```351352### Vercel353354```typescript355// api/[[...route]].ts356import { Hono } from 'hono'357import { handle } from 'hono/vercel'358359const app = new Hono().basePath('/api')360app.get('/hello', (c) => c.json({ msg: 'Hello from Vercel' }))361362export const GET = handle(app)363export const POST = handle(app)364```365366نفس كود الأعمال، نفس التوجيه، نفس الوسطاء. يتغير المحول فقط.367368## مثال عملي: REST API مع المصادقة وقاعدة البيانات369370```typescript371import { Hono } from 'hono'372import { jwt } from 'hono/jwt'373import { logger } from 'hono/logger'374import { cors } from 'hono/cors'375import { zValidator } from '@hono/zod-validator'376import { z } from 'zod'377378type Bindings = { DB: D1Database; JWT_SECRET: string }379type Variables = { jwtPayload: { sub: string } }380381const app = new Hono<{ Bindings: Bindings; Variables: Variables }>()382383app.use('*', logger())384app.use('/api/*', cors({ origin: 'https://spinny.dev', credentials: true }))385386const auth = (c: any, next: any) =>387 jwt({ secret: c.env.JWT_SECRET })(c, next)388389const api = app.basePath('/api')390391api.get('/posts', async (c) => {392 const { results } = await c.env.DB393 .prepare('SELECT id, title, created_at FROM posts ORDER BY created_at DESC LIMIT 50')394 .all()395 return c.json({ posts: results })396})397398api.post(399 '/posts',400 auth,401 zValidator('json', z.object({402 title: z.string().min(1).max(200),403 body: z.string().min(1),404 })),405 async (c) => {406 const { title, body } = c.req.valid('json')407 const userId = c.var.jwtPayload.sub408 const result = await c.env.DB409 .prepare('INSERT INTO posts (title, body, author_id) VALUES (?, ?, ?) RETURNING id')410 .bind(title, body, userId)411 .first<{ id: number }>()412 return c.json({ id: result?.id }, 201)413 }414)415416api.onError((err, c) => {417 console.error(err)418 return c.json({ error: 'Internal error' }, 500)419})420421export type AppType = typeof api422export default app423```424425عميل React يستهلكها بأمان نوعي كامل:426427```typescript428import { hc } from 'hono/client'429import type { AppType } from '../api/src/index'430431const api = hc<AppType>(import.meta.env.VITE_API_URL)432433const res = await api.posts.$post({434 json: { title: 'Hono في 2026', body: '...' },435}, {436 headers: { Authorization: `Bearer ${token}` },437})438```439440## الاختبار441442```typescript443import { describe, it, expect } from 'vitest'444import app from '../src/index'445446describe('GET /api/posts', () => {447 it('يعيد قائمة المنشورات', async () => {448 const res = await app.request('/api/posts')449 expect(res.status).toBe(200)450 const body = await res.json()451 expect(body.posts).toBeInstanceOf(Array)452 })453})454```455456`app.request()` يسمح باختبار المسارات دون تشغيل خادم HTTP.457458## أفضل الممارسات459460### 1. سلسلة تعريفات المسارات461462```typescript463const app = new Hono()464 .get('/posts', handler1)465 .post('/posts', handler2)466 .get('/posts/:id', handler3)467```468469السلسلة تحافظ على نوع `app` محدثاً مع كل مسار، أمر جوهري لعميل RPC.470471### 2. صدّر النوع، لا التطبيق472473يجب أن يستورد العميل `AppType` لا التطبيق. `import type` يضمن عدم تضمين كود الواجهة الخلفية في بناء الواجهة الأمامية.474475### 3. موجه واحد لكل مجال476477تطبيق فرعي لـ `posts`، آخر لـ `users`، آخر لـ `webhooks`. ركّبهم بـ `app.route()`.478479### 4. التحقق على الحافة، دائماً480481كل مدخل خارجي (body، query، header) يجب أن يمر عبر `zValidator`.482483### 5. اعتمد على الروابط، لا على العملاء العالميين484485على Cloudflare، الوصول لـ KV/D1/R2 عبر `c.env`.486487### 6. قِس قبل تحسين الموجه488489`SmartRouter` الافتراضي مناسب لـ 95٪ من الحالات.490491## الخلاصة492493أصبح Hono في 2026 المعيار الفعلي لبناء APIs جاهزة للحافة بـ TypeScript. الجمع بين معايير الويب والأداء والأمان النوعي وقابلية النقل يحل بالضبط المشكلات التي أعاقت الأطر التقليدية.494495> **قائمة التحقق للبدء:**496>497> - [x] `npm create hono@latest` واختر قالب بيئة التشغيل498> - [x] عرّف المسارات بالسلسلة (`.get(...).post(...)`)499> - [x] أضف `logger`، `cors`، `secureHeaders` كوسطاء عامين500> - [x] تحقق من كل مدخل بـ `@hono/zod-validator`501> - [x] صدّر `AppType` واستهلك الـ API بعميل `hc` الآمن نوعياً502> - [x] اكتب الاختبارات بـ `app.request()` — دون خادم HTTP503> - [x] انشر بـ `wrangler deploy` (CF) أو `vercel deploy` أو حزمة بيئتك504
:Hono: إطار الويب فائق السرعة المبني على معايير الويبlines 1-504 (END) — press q to close