بناء واجهات REST API باستخدام Hono و Bun: البديل العصري لـ Express

بناء واجهات REST API باستخدام Hono و Bun
شهدت منظومة JavaScript تطوراً هائلاً في السنوات الأخيرة. بينما خدمنا Express.js بشكل ممتاز لأكثر من عقد، ظهرت بدائل حديثة تقدم أداءً أفضل وأماناً للأنواع وتجربة مطور متميزة. في هذا الدرس الشامل، سنستكشف Hono - إطار عمل فائق السرعة - مع Bun - بيئة تشغيل JavaScript السريعة.
لماذا Hono مع Bun؟
قبل الغوص في الكود، دعنا نفهم سبب انتشار هذا الثنائي بسرعة:
مميزات بيئة Bun
- السرعة: أسرع بـ 4 مرات من Node.js في كثير من العمليات
- TypeScript مدمج: لا حاجة لأي ترجمة
- مجمّع مدمج: لا حاجة لـ webpack أو esbuild
- SQLite مدمج: قاعدة بيانات جاهزة للاستخدام
- متوافق مع npm: استخدم حزمك المفضلة
مميزات إطار Hono
- خفيف جداً: حوالي 14 كيلوبايت فقط، بدون اعتماديات
- متعدد البيئات: يعمل على Bun و Node و Deno و Cloudflare Workers
- آمن للأنواع: دعم كامل لـ TypeScript
- واجهة حديثة: Middleware والتوجيه والتحقق مدمجة
- معايير الويب: يستخدم Fetch API و Request/Response
المتطلبات الأساسية
قبل البدء، تأكد من:
- معرفة أساسية بـ JavaScript/TypeScript
- إلمام بمفاهيم REST API
- راحة في استخدام سطر الأوامر
- محرر كود (VS Code موصى به)
يستخدم هذا الدرس Bun 1.1+ و Hono 4.x. قد تختلف بعض الأوامر مع إصدارات مختلفة.
الخطوة 1: تثبيت Bun
إذا لم تثبّت Bun بعد، فالأمر بسيط:
# macOS و Linux
curl -fsSL https://bun.sh/install | bash
# Windows (باستخدام PowerShell)
powershell -c "irm bun.sh/install.ps1 | iex"تحقق من التثبيت:
bun --version
# يجب أن يظهر: 1.1.x أو أعلىالخطوة 2: إنشاء المشروع
لنبدأ بإنشاء مشروع جديد:
# إنشاء مجلد المشروع
mkdir hono-api && cd hono-api
# تهيئة المشروع مع Bun
bun init -y
# تثبيت Hono
bun add hono
# تثبيت اعتماديات التطوير
bun add -d @types/bunيجب أن تكون بنية المشروع:
hono-api/
├── node_modules/
├── package.json
├── tsconfig.json
├── bun.lockb
└── index.ts
الخطوة 3: أول خادم Hono
استبدل محتوى index.ts بـ:
import { Hono } from 'hono'
const app = new Hono()
// المسار الرئيسي
app.get('/', (c) => {
return c.json({
message: 'مرحباً بك في Hono API!',
version: '1.0.0',
timestamp: new Date().toISOString()
})
})
// نقطة فحص الصحة
app.get('/health', (c) => {
return c.json({ status: 'healthy' })
})
// تشغيل الخادم
export default {
port: 3000,
fetch: app.fetch
}شغّل الخادم:
bun run index.tsزر http://localhost:3000 - يجب أن ترى الاستجابة بصيغة JSON!
يدعم Bun إعادة التحميل التلقائي. استخدم bun --watch index.ts أثناء التطوير لإعادة التشغيل التلقائي.
الخطوة 4: تنظيم بنية الـ API
لـ API جاهز للإنتاج، لننظم الكود بشكل صحيح. أنشئ هذه البنية:
hono-api/
├── src/
│ ├── index.ts
│ ├── routes/
│ │ ├── index.ts
│ │ ├── users.ts
│ │ └── products.ts
│ ├── middleware/
│ │ ├── auth.ts
│ │ └── logger.ts
│ ├── services/
│ │ └── database.ts
│ └── types/
│ └── index.ts
├── package.json
└── tsconfig.json
أنشئ المجلدات والملفات:
mkdir -p src/{routes,middleware,services,types}
touch src/index.ts
touch src/routes/{index,users,products}.ts
touch src/middleware/{auth,logger}.ts
touch src/services/database.ts
touch src/types/index.tsالخطوة 5: تعريف الأنواع
في src/types/index.ts:
export interface User {
id: string
name: string
email: string
createdAt: Date
}
export interface Product {
id: string
name: string
price: number
description: string
stock: number
}
export interface ApiResponse<T> {
success: boolean
data?: T
error?: string
meta?: {
page?: number
total?: number
}
}الخطوة 6: إنشاء Middleware للتسجيل
في src/middleware/logger.ts:
import { MiddlewareHandler } from 'hono'
export const logger: MiddlewareHandler = async (c, next) => {
const start = Date.now()
const method = c.req.method
const path = c.req.path
console.log(`→ ${method} ${path}`)
await next()
const duration = Date.now() - start
const status = c.res.status
console.log(`← ${method} ${path} ${status} (${duration}ms)`)
}الخطوة 7: بناء Middleware للمصادقة
في src/middleware/auth.ts:
import { MiddlewareHandler } from 'hono'
import { HTTPException } from 'hono/http-exception'
// مصادقة بسيطة بمفتاح API
export const authMiddleware: MiddlewareHandler = async (c, next) => {
const apiKey = c.req.header('X-API-Key')
if (!apiKey) {
throw new HTTPException(401, {
message: 'مفتاح API مطلوب'
})
}
// في الإنتاج، تحقق من قاعدة البيانات
const validKeys = ['demo-key-123', 'test-key-456']
if (!validKeys.includes(apiKey)) {
throw new HTTPException(403, {
message: 'مفتاح API غير صالح'
})
}
// إرفاق معلومات المستخدم بالسياق
c.set('apiKey', apiKey)
await next()
}
// مثال على مصادقة JWT
export const jwtAuth: MiddlewareHandler = async (c, next) => {
const authHeader = c.req.header('Authorization')
if (!authHeader?.startsWith('Bearer ')) {
throw new HTTPException(401, {
message: 'رمز Bearer مطلوب'
})
}
const token = authHeader.slice(7)
try {
// في الإنتاج، تحقق من JWT بشكل صحيح
// const payload = await verifyJwt(token)
// c.set('user', payload)
await next()
} catch {
throw new HTTPException(401, {
message: 'رمز غير صالح'
})
}
}الخطوة 8: إعداد خدمة قاعدة البيانات
نستخدم SQLite المدمج في Bun. في src/services/database.ts:
import { Database } from 'bun:sqlite'
import type { User, Product } from '../types'
// تهيئة قاعدة البيانات
const db = new Database('app.db')
// إنشاء الجداول
db.run(`
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`)
db.run(`
CREATE TABLE IF NOT EXISTS products (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
price REAL NOT NULL,
description TEXT,
stock INTEGER DEFAULT 0
)
`)
// عمليات المستخدمين
export const userService = {
findAll(): User[] {
return db.query('SELECT * FROM users').all() as User[]
},
findById(id: string): User | null {
return db.query('SELECT * FROM users WHERE id = ?').get(id) as User | null
},
create(user: Omit<User, 'createdAt'>): User {
const stmt = db.prepare(
'INSERT INTO users (id, name, email) VALUES (?, ?, ?)'
)
stmt.run(user.id, user.name, user.email)
return this.findById(user.id)!
},
update(id: string, data: Partial<User>): User | null {
const fields = Object.keys(data)
.map(k => `${k} = ?`)
.join(', ')
const values = Object.values(data)
db.prepare(`UPDATE users SET ${fields} WHERE id = ?`).run(...values, id)
return this.findById(id)
},
delete(id: string): boolean {
const result = db.prepare('DELETE FROM users WHERE id = ?').run(id)
return result.changes > 0
}
}
// عمليات المنتجات
export const productService = {
findAll(): Product[] {
return db.query('SELECT * FROM products').all() as Product[]
},
findById(id: string): Product | null {
return db.query('SELECT * FROM products WHERE id = ?').get(id) as Product | null
},
create(product: Product): Product {
const stmt = db.prepare(
'INSERT INTO products (id, name, price, description, stock) VALUES (?, ?, ?, ?, ?)'
)
stmt.run(product.id, product.name, product.price, product.description, product.stock)
return this.findById(product.id)!
},
update(id: string, data: Partial<Product>): Product | null {
const fields = Object.keys(data)
.map(k => `${k} = ?`)
.join(', ')
const values = Object.values(data)
db.prepare(`UPDATE products SET ${fields} WHERE id = ?`).run(...values, id)
return this.findById(id)
},
delete(id: string): boolean {
const result = db.prepare('DELETE FROM products WHERE id = ?').run(id)
return result.changes > 0
}
}
export { db }الخطوة 9: إنشاء مسارات المستخدمين
في src/routes/users.ts:
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
import { userService } from '../services/database'
import type { ApiResponse, User } from '../types'
const users = new Hono()
// مخططات التحقق
const createUserSchema = z.object({
name: z.string().min(2).max(100),
email: z.string().email()
})
const updateUserSchema = z.object({
name: z.string().min(2).max(100).optional(),
email: z.string().email().optional()
})
// GET /users - قائمة المستخدمين
users.get('/', (c) => {
const allUsers = userService.findAll()
const response: ApiResponse<User[]> = {
success: true,
data: allUsers,
meta: { total: allUsers.length }
}
return c.json(response)
})
// GET /users/:id - مستخدم واحد
users.get('/:id', (c) => {
const id = c.req.param('id')
const user = userService.findById(id)
if (!user) {
return c.json<ApiResponse<null>>({
success: false,
error: 'المستخدم غير موجود'
}, 404)
}
return c.json<ApiResponse<User>>({
success: true,
data: user
})
})
// POST /users - إنشاء مستخدم
users.post(
'/',
zValidator('json', createUserSchema),
(c) => {
const body = c.req.valid('json')
const id = crypto.randomUUID()
try {
const user = userService.create({ id, ...body })
return c.json<ApiResponse<User>>({
success: true,
data: user
}, 201)
} catch (error) {
return c.json<ApiResponse<null>>({
success: false,
error: 'البريد الإلكتروني موجود مسبقاً'
}, 409)
}
}
)
// PUT /users/:id - تحديث مستخدم
users.put(
'/:id',
zValidator('json', updateUserSchema),
(c) => {
const id = c.req.param('id')
const body = c.req.valid('json')
const user = userService.update(id, body)
if (!user) {
return c.json<ApiResponse<null>>({
success: false,
error: 'المستخدم غير موجود'
}, 404)
}
return c.json<ApiResponse<User>>({
success: true,
data: user
})
}
)
// DELETE /users/:id - حذف مستخدم
users.delete('/:id', (c) => {
const id = c.req.param('id')
const deleted = userService.delete(id)
if (!deleted) {
return c.json<ApiResponse<null>>({
success: false,
error: 'المستخدم غير موجود'
}, 404)
}
return c.json<ApiResponse<null>>({
success: true
}, 204)
})
export default usersلا تنسَ تثبيت Zod للتحقق:
bun add zod @hono/zod-validatorالخطوة 10: إنشاء مسارات المنتجات
في src/routes/products.ts:
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
import { productService } from '../services/database'
import type { ApiResponse, Product } from '../types'
const products = new Hono()
const productSchema = z.object({
name: z.string().min(1).max(200),
price: z.number().positive(),
description: z.string().optional(),
stock: z.number().int().min(0).default(0)
})
// GET /products
products.get('/', (c) => {
const query = c.req.query('search')
let allProducts = productService.findAll()
if (query) {
allProducts = allProducts.filter(p =>
p.name.toLowerCase().includes(query.toLowerCase())
)
}
return c.json<ApiResponse<Product[]>>({
success: true,
data: allProducts,
meta: { total: allProducts.length }
})
})
// GET /products/:id
products.get('/:id', (c) => {
const product = productService.findById(c.req.param('id'))
if (!product) {
return c.json<ApiResponse<null>>({
success: false,
error: 'المنتج غير موجود'
}, 404)
}
return c.json<ApiResponse<Product>>({
success: true,
data: product
})
})
// POST /products
products.post(
'/',
zValidator('json', productSchema),
(c) => {
const body = c.req.valid('json')
const id = crypto.randomUUID()
const product = productService.create({
id,
name: body.name,
price: body.price,
description: body.description || '',
stock: body.stock
})
return c.json<ApiResponse<Product>>({
success: true,
data: product
}, 201)
}
)
// PUT /products/:id
products.put(
'/:id',
zValidator('json', productSchema.partial()),
(c) => {
const id = c.req.param('id')
const body = c.req.valid('json')
const product = productService.update(id, body)
if (!product) {
return c.json<ApiResponse<null>>({
success: false,
error: 'المنتج غير موجود'
}, 404)
}
return c.json<ApiResponse<Product>>({
success: true,
data: product
})
}
)
// DELETE /products/:id
products.delete('/:id', (c) => {
const deleted = productService.delete(c.req.param('id'))
if (!deleted) {
return c.json<ApiResponse<null>>({
success: false,
error: 'المنتج غير موجود'
}, 404)
}
return c.json({ success: true }, 204)
})
export default productsالخطوة 11: دمج المسارات
في src/routes/index.ts:
import { Hono } from 'hono'
import users from './users'
import products from './products'
const routes = new Hono()
routes.route('/users', users)
routes.route('/products', products)
export default routesالخطوة 12: نقطة الدخول الرئيسية
حدّث src/index.ts:
import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { prettyJSON } from 'hono/pretty-json'
import { HTTPException } from 'hono/http-exception'
import { logger } from './middleware/logger'
import { authMiddleware } from './middleware/auth'
import routes from './routes'
const app = new Hono()
// Middleware عام
app.use('*', logger)
app.use('*', cors({
origin: ['http://localhost:3000', 'https://yourdomain.com'],
allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowHeaders: ['Content-Type', 'Authorization', 'X-API-Key']
}))
app.use('*', prettyJSON())
// المسارات العامة
app.get('/', (c) => {
return c.json({
name: 'Hono REST API',
version: '1.0.0',
docs: '/docs',
health: '/health'
})
})
app.get('/health', (c) => c.json({ status: 'ok' }))
// مسارات API المحمية
app.use('/api/*', authMiddleware)
app.route('/api', routes)
// معالج الأخطاء العام
app.onError((err, c) => {
console.error('خطأ:', err)
if (err instanceof HTTPException) {
return c.json({
success: false,
error: err.message
}, err.status)
}
return c.json({
success: false,
error: 'خطأ في الخادم الداخلي'
}, 500)
})
// معالج 404
app.notFound((c) => {
return c.json({
success: false,
error: 'المسار غير موجود'
}, 404)
})
// تصدير لـ Bun
export default {
port: process.env.PORT || 3000,
fetch: app.fetch
}
console.log('🚀 الخادم يعمل على http://localhost:3000')الخطوة 13: اختبار الـ API
شغّل الخادم:
bun run src/index.tsاختبر باستخدام curl:
# فحص الصحة
curl http://localhost:3000/health
# إنشاء مستخدم (مع مفتاح API)
curl -X POST http://localhost:3000/api/users \
-H "Content-Type: application/json" \
-H "X-API-Key: demo-key-123" \
-d '{"name": "أحمد محمد", "email": "ahmed@example.com"}'
# قائمة المستخدمين
curl http://localhost:3000/api/users \
-H "X-API-Key: demo-key-123"
# إنشاء منتج
curl -X POST http://localhost:3000/api/products \
-H "Content-Type: application/json" \
-H "X-API-Key: demo-key-123" \
-d '{"name": "حاسوب محمول", "price": 999.99, "stock": 50}'لا تقم أبداً بحفظ مفاتيح API في نظام التحكم بالإصدار. استخدم متغيرات البيئة للبيانات الحساسة في الإنتاج.
الخطوة 14: إضافة توثيق OpenAPI
يدعم Hono توثيق OpenAPI/Swagger. ثبّت الحزمة:
bun add @hono/swagger-uiأضف نقطة التوثيق إلى src/index.ts:
import { swaggerUI } from '@hono/swagger-ui'
// أضف بعد المسارات الأخرى
app.get('/docs', swaggerUI({ url: '/openapi.json' }))
app.get('/openapi.json', (c) => {
return c.json({
openapi: '3.0.0',
info: {
title: 'Hono REST API',
version: '1.0.0',
description: 'واجهة REST API حديثة مبنية بـ Hono و Bun'
},
servers: [
{ url: 'http://localhost:3000', description: 'التطوير' }
],
paths: {
'/api/users': {
get: {
summary: 'قائمة جميع المستخدمين',
security: [{ apiKey: [] }],
responses: {
'200': { description: 'قائمة المستخدمين' }
}
}
}
// أضف المزيد من المسارات...
},
components: {
securitySchemes: {
apiKey: {
type: 'apiKey',
in: 'header',
name: 'X-API-Key'
}
}
}
})
})الخطوة 15: النشر
الخيار 1: Docker
أنشئ Dockerfile:
FROM oven/bun:1.1-alpine
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile --production
COPY . .
ENV NODE_ENV=production
EXPOSE 3000
CMD ["bun", "run", "src/index.ts"]ابنِ وشغّل:
docker build -t hono-api .
docker run -p 3000:3000 hono-apiالخيار 2: Fly.io
أنشئ fly.toml:
app = "your-hono-api"
primary_region = "fra"
[build]
builder = "oven/bun:1.1"
[http_service]
internal_port = 3000
force_https = true
[env]
NODE_ENV = "production"انشر:
fly launch
fly deployالخيار 3: Cloudflare Workers
يعمل Hono بسلاسة مع Workers. حدّث التصدير:
export default appانشر مع Wrangler:
bunx wrangler deployمقارنة الأداء
إليك لماذا يتفوق Hono + Bun على Express + Node:
| المقياس | Express + Node | Hono + Bun |
|---|---|---|
| طلبات/ثانية | ~15,000 | ~90,000 |
| وقت البدء | ~300ms | ~25ms |
| استخدام الذاكرة | ~50MB | ~20MB |
| حجم الحزمة | ثقيل | ~14KB |
ملخص
لقد بنيت REST API كاملاً مع:
- ✅ إطار Hono للتوجيه والـ Middleware
- ✅ بيئة Bun لأقصى أداء
- ✅ TypeScript لأمان الأنواع
- ✅ قاعدة بيانات SQLite مع عمليات CRUD
- ✅ التحقق من المدخلات مع Zod
- ✅ Middleware للمصادقة
- ✅ CORS ومعالجة الأخطاء
- ✅ توثيق OpenAPI
- ✅ خيارات النشر
الخطوات التالية
- أضف تحديد معدل الطلبات مع
hono/rate-limit - طبّق مصادقة JWT كاملة
- أضف ترحيل قاعدة البيانات
- أعد خط أنابيب CI/CD
- أضف اختبارات الوحدات والتكامل
يمثل الجمع بين Hono و Bun مستقبل تطوير الـ Backend بـ JavaScript - سريع وآمن للأنواع وصديق للمطور. ابدأ البناء!
المصادر
ناقش مشروعك معنا
نحن هنا للمساعدة في احتياجات تطوير الويب الخاصة بك. حدد موعدًا لمناقشة مشروعك وكيف يمكننا مساعدتك.
دعنا نجد أفضل الحلول لاحتياجاتك.
مقالات ذات صلة

AI SDK 4.0: الميزات الجديدة وحالات الاستخدام
اكتشف الميزات الجديدة وحالات الاستخدام لـ AI SDK 4.0، بما في ذلك دعم PDF واستخدام الكمبيوتر والمزيد.

مقدمة في بروتوكول سياق النموذج (MCP)
تعلم عن بروتوكول سياق النموذج (MCP)، وحالات استخدامه، ومزاياه، وكيفية بناء واستخدام خادم MCP مع TypeScript.

الحساب البنكي المجاني للمبادرين الذاتيين والمحترفين مع فُلوسي
اكتشف كيف يوفر حساب فُلوسي الاحترافي المجاني حلاً مصرفيًا رقميًا شاملاً للمبادرين الذاتيين وأصحاب المهن الحرة في تونس، مع ميزات مصممة لتبسيط الإدارة المالية.