spinny:~/writing $ vim hono-framework-guide.md
1~2لسنوات، كان اختيار إطار ويب JavaScript يعني قبول مقايضة: Express عام لكنه بطيء ومرتبط بـ Node، و Fastify سريع لكنه فقط لـ Node، و Next.js كامل لكنه ثقيل. عندما ظهرت بيئات تشغيل الحافة — Cloudflare Workers و Deno Deploy و Bun و Vercel Edge — أظهرت تلك الأطر حدودها: تبعيات غير متوافقة، حزم ضخمة، APIs مرتبطة بـ `req`/`res` الخاصة بـ Node.3~4[Hono](https://hono.dev) (تعني "اللهب" 🔥 بالـيابانية) هي الإجابة الحديثة. إطار يقل حجمه عن 14KB، مبني بالكامل على معايير الويب (`Request`، `Response`، `fetch`)، ويعمل في أي مكان توجد فيه بيئة تشغيل JavaScript. نفس الكود يُنشر على Cloudflare Workers و Bun و Deno و Node.js و Vercel و Netlify و AWS Lambda — دون تغيير.5~6## لماذا Hono7~8يقوم Hono بثلاثة أشياء أفضل من أي شخص آخر:9~101. **الأداء.** يقوم `RegExpRouter` بتجميع كل أنماط التوجيه في تعبير منتظم واحد، متجنباً الحلقات الخطية للموجهات التقليدية. تتجاوز المعايير 400000 عملية في الثانية، مما يضع Hono ضمن أسرع الموجهات في نظام JavaScript البيئي.112. **قابلية النقل.** معايير الويب تعني صفر تبعيات على Node. نفس `app.fetch` يُصدَّر افتراضياً في Cloudflare Worker، ويُمرَّر إلى `Bun.serve`، ويُحمَّل في خادم Deno، أو يُكيَّف مع `@hono/node-server`.123. **DX يعتمد TypeScript أولاً.** معاملات المسار مستنتجة كأنواع حرفية، عميل 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~54على Cloudflare Workers هذا يكفي. على Bun: `Bun.serve({ fetch: app.fetch, port: 3000 })`. على Node: `serve({ fetch: app.fetch })` من `@hono/node-server`. السطح متطابق.55~56## التوجيه57~58تُعلَن المسارات بطرق فعل HTTP وتدعم المعاملات والأحرف البديلة والتعبيرات المنتظمة.59~60```typescript61import { Hono } from 'hono'62~63const app = new Hono()64~65app.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```80~81تُستنتج المعاملات كأنواع حرفية: يعرف TypeScript أن `c.req.param('id')` يُعيد `string` فقط إذا أعلنت `:id` في النمط. الخطأ في التهجئة هو خطأ وقت التجميع.82~83### تجميع المسارات84~85`app.route()` يسمح بتركيب التطبيقات الفرعية كوحدات، كل واحدة بادئتها الخاصة.86~87```typescript88// routes/posts.ts89import { Hono } from 'hono'90~91const posts = new Hono()92posts.get('/', (c) => c.json({ posts: [] }))93posts.get('/:id', (c) => c.json({ id: c.req.param('id') }))94export default posts95~96// src/index.ts97import { Hono } from 'hono'98import posts from './routes/posts'99~100const app = new Hono()101app.route('/posts', posts)102```103~104ترث المسارات المتداخلة المسار الأساسي ونوع تطبيق الجذر، لذلك يرى عميل RPC البنية كاملة.105~106## كائن Context107~108كل معالج يستقبل `c` — Context للطلب الحالي. هو الـ API الوحيد الذي تحتاج تعلمه لقراءة المدخلات وإنتاج المخرجات.109~110```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)117~118 // متغيرات مشتركة بين الوسيط والمعالج119 c.set('requestId', crypto.randomUUID())120 const id = c.get('requestId')121~122 // الاستجابة123 c.header('X-Request-Id', id)124 c.status(200)125 return c.json({ userAgent, page, body, id })126})127```128~129طرق الاستجابة الرئيسية هي `c.text` و `c.json` و `c.html` و `c.body` (raw) و `c.redirect`. كلها تقبل رمز حالة كوسيطة ثانية.130~131## الوسطاء: نموذج البصلة132~133الوسطاء هي دوال `(c, next) => ...` يمكنها تشغيل كود قبل المعالج وبعده. عند تركيبها تشكل نموذج البصلة الكلاسيكي: أول وسيط مسجل هو أول من يبدأ وآخر من ينتهي.134~135```typescript136import { Hono } from 'hono'137import { logger } from 'hono/logger'138import { cors } from 'hono/cors'139import { secureHeaders } from 'hono/secure-headers'140~141const app = new Hono()142~143app.use('*', logger())144app.use('*', secureHeaders())145app.use('/api/*', cors({ origin: 'https://spinny.dev' }))146~147app.use('*', async (c, next) => {148 const start = performance.now()149 await next()150 c.header('X-Response-Time', `${performance.now() - start}ms`)151})152```153~154`await next()` يُسلِّم التحكم للوسيط التالي. كل ما بعده يعمل في مرحلة الاستجابة، بعد أن ينتج المعالج نتيجة. إعادة `Response` قبل `next()` يقصر السلسلة.155~156### الوسطاء المدمجون157~158| الوسيط | الغرض |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 للتحليل |171~172### وسطاء مخصصون آمنون نوعياً173~174```typescript175import { createMiddleware } from 'hono/factory'176~177type AuthVars = { userId: string; role: 'user' | 'admin' }178~179export 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)183~184 const payload = await verifyJwt(token)185 c.set('userId', payload.sub)186 c.set('role', payload.role)187 await next()188 }189)190~191app.get('/me', requireAuth, (c) => {192 const userId = c.var.userId // string، مكتوب نوعياً193 return c.json({ userId })194})195```196~197## التحقق بـ Zod198~199```typescript200import { Hono } from 'hono'201import { zValidator } from '@hono/zod-validator'202import { z } from 'zod'203~204const 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})209~210app.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```219~220إذا فشل الجسم في التحقق، يستجيب Hono بـ 400 مع خطأ Zod قبل حتى استدعاء المعالج. يمكن أيضاً التحقق من `query` و `param` و `header` و `cookie` و `form`.221~222## RPC: عميل آمن نوعياً من طرف لطرف223~224```typescript225// server.ts226import { Hono } from 'hono'227import { zValidator } from '@hono/zod-validator'228import { z } from 'zod'229~230const 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 )239~240export type AppType = typeof app241export default app242```243~244```typescript245// client.ts246import { hc } from 'hono/client'247import type { AppType } from './server'248~249const client = hc<AppType>('https://api.spinny.dev')250~251const res = await client.posts[':id'].$get({ param: { id: '42' } })252if (res.ok) {253 const data = await res.json()254 console.log(data.title)255}256~257const created = await client.posts.$post({258 json: { title: 'مرحبا', body: 'Hono لهب' },259})260```261~262أعد تسمية مسار على الخادم وسيُكسر TypeScript الخاص بالعميل فوراً في CI. نفس ميزة tRPC، لكن عبر HTTP المعياري، دون وسطاء محددين، ومع حزمة صغيرة جداً.263~264### تمييز رموز الحالة265~266```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```273~274```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```283~284## الموجهات والأداء285~286| الموجه | نقاط القوة | متى يستخدم |287|--------|-----------|-------------|288| `RegExpRouter` | أعلى سرعة، regex مجمع | افتراضي لمعظم APIs |289| `TrieRouter` | يدعم كل الأنماط | الأنماط المعقدة التي لا يتعامل معها RegExp |290| `SmartRouter` | يختار الأفضل تلقائياً | الافتراضي الموصى به |291| `LinearRouter` | تسجيل فائق السرعة | عمال one-shot، البدء البارد حرج |292| `PatternRouter` | حزمة صغرى (<15KB) | قيود حجم متطرفة |293~294```typescript295import { Hono } from 'hono'296import { LinearRouter } from 'hono/router/linear-router'297~298const app = new Hono({ router: new LinearRouter() })299```300~301## النشر متعدد البيئات302~303### Cloudflare Workers304~305```typescript306import { Hono } from 'hono'307~308type Bindings = { MY_KV: KVNamespace; DB: D1Database }309const app = new Hono<{ Bindings: Bindings }>()310~311app.get('/cache/:key', async (c) => {312 const value = await c.env.MY_KV.get(c.req.param('key'))313 return c.json({ value })314})315~316export default app317```318~319نشر: `npx wrangler deploy`.320~321### Bun322~323```typescript324import { Hono } from 'hono'325const app = new Hono()326app.get('/', (c) => c.text('Bun + Hono'))327~328Bun.serve({ fetch: app.fetch, port: 3000 })329```330~331### Node.js332~333```typescript334import { serve } from '@hono/node-server'335import { Hono } from 'hono'336~337const app = new Hono()338app.get('/', (c) => c.text('Node + Hono'))339~340serve({ fetch: app.fetch, port: 3000 })341```342~343### Deno344~345```typescript346import { Hono } from 'jsr:@hono/hono'347const app = new Hono()348app.get('/', (c) => c.text('Deno + Hono'))349Deno.serve(app.fetch)350```351~352### Vercel353~354```typescript355// api/[[...route]].ts356import { Hono } from 'hono'357import { handle } from 'hono/vercel'358~359const app = new Hono().basePath('/api')360app.get('/hello', (c) => c.json({ msg: 'Hello from Vercel' }))361~362export const GET = handle(app)363export const POST = handle(app)364```365~366نفس كود الأعمال، نفس التوجيه، نفس الوسطاء. يتغير المحول فقط.367~368## مثال عملي: REST API مع المصادقة وقاعدة البيانات369~370```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'377~378type Bindings = { DB: D1Database; JWT_SECRET: string }379type Variables = { jwtPayload: { sub: string } }380~381const app = new Hono<{ Bindings: Bindings; Variables: Variables }>()382~383app.use('*', logger())384app.use('/api/*', cors({ origin: 'https://spinny.dev', credentials: true }))385~386const auth = (c: any, next: any) =>387 jwt({ secret: c.env.JWT_SECRET })(c, next)388~389const api = app.basePath('/api')390~391api.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})397~398api.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)415~416api.onError((err, c) => {417 console.error(err)418 return c.json({ error: 'Internal error' }, 500)419})420~421export type AppType = typeof api422export default app423```424~425عميل React يستهلكها بأمان نوعي كامل:426~427```typescript428import { hc } from 'hono/client'429import type { AppType } from '../api/src/index'430~431const api = hc<AppType>(import.meta.env.VITE_API_URL)432~433const res = await api.posts.$post({434 json: { title: 'Hono في 2026', body: '...' },435}, {436 headers: { Authorization: `Bearer ${token}` },437})438```439~440## الاختبار441~442```typescript443import { describe, it, expect } from 'vitest'444import app from '../src/index'445~446describe('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```455~456`app.request()` يسمح باختبار المسارات دون تشغيل خادم HTTP.457~458## أفضل الممارسات459~460### 1. سلسلة تعريفات المسارات461~462```typescript463const app = new Hono()464 .get('/posts', handler1)465 .post('/posts', handler2)466 .get('/posts/:id', handler3)467```468~469السلسلة تحافظ على نوع `app` محدثاً مع كل مسار، أمر جوهري لعميل RPC.470~471### 2. صدّر النوع، لا التطبيق472~473يجب أن يستورد العميل `AppType` لا التطبيق. `import type` يضمن عدم تضمين كود الواجهة الخلفية في بناء الواجهة الأمامية.474~475### 3. موجه واحد لكل مجال476~477تطبيق فرعي لـ `posts`، آخر لـ `users`، آخر لـ `webhooks`. ركّبهم بـ `app.route()`.478~479### 4. التحقق على الحافة، دائماً480~481كل مدخل خارجي (body، query، header) يجب أن يمر عبر `zValidator`.482~483### 5. اعتمد على الروابط، لا على العملاء العالميين484~485على Cloudflare، الوصول لـ KV/D1/R2 عبر `c.env`.486~487### 6. قِس قبل تحسين الموجه488~489`SmartRouter` الافتراضي مناسب لـ 95٪ من الحالات.490~491## الخلاصة492~493أصبح Hono في 2026 المعيار الفعلي لبناء APIs جاهزة للحافة بـ TypeScript. الجمع بين معايير الويب والأداء والأمان النوعي وقابلية النقل يحل بالضبط المشكلات التي أعاقت الأطر التقليدية.494~495> **قائمة التحقق للبدء:**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~
NORMAL · hono-framework-guide.md [readonly]504 lines · :q to close