محرك الخادم Nitro
يحتاج Charlie إلى إضافة طبقة تخزين مؤقت إلى MegaShop—استعلامات قاعدة البيانات المتكررة على صفحة عرض ملايين المنتجات بطيئة جدًا. اكتشف Bob أن طرق التخزين تختلف بين بيئات النشر (Node، Docker، Vercel). يوفر محرك Nitro طبقة تجريد تخزين موحدة وواجهة تخزين مؤقت، مما يسمح لقاعدة كود واحدة بالعمل في جميع البيئات.
1. ما ستتعلمه
- بنية Nitro: Rollup Build + توجيه H3 + فصل وقت التشغيل
- طبقة التخزين: useStorage() + دعم متعدد البرامج لـ KV وRedis وFileSystem وOSS
- واجهة التخزين المؤقت: cachedEventHandler / defineCachedFunction
- خطافات الأحداث: دورة حياة nitro.hooks
- تخزين مؤقت لمنتجات MegaShop + تحديثات المخزون + نشر بإعدادات متعددة
2. قصة حقيقية لمهندس معماري
(1) نقطة الألم: استعلامات ملايين المنتجات ترهق قاعدة البيانات
تقوم صفحة تفاصيل منتج MegaShop بتنفيذ 2000 استعلام قاعدة بيانات في الثانية بزمن استجابة 500 مللي ثانية. أضاف Bob تخزينًا مؤقتًا لـ Redis، لكن الكود مقترن بإحكام مع بيئة النشر—يستخدم تخزينًا مؤقتًا قائمًا على الملفات محليًا وRedis في الإنتاج، مما يتطلب مجموعتين منفصلتين من تغييرات الكود.
(2) حلول لطبقة تجريد تخزين Nitro
تتيح واجهة التخزين الموحدة useStorage() من Nitro تبديل البرامج دون تغيير الكود:
// نفس الكود، برنامج مختلف بناءً على الإعداد
const storage = useStorage('products')
await storage.setItem('product:123', productData)
(3) الفوائد: واجهة موحدة + زمن استجابة 5 مللي ثانية
تم توحيد كود طبقة التخزين: يُستخدم الذاكرة/نظام الملفات للتطوير المحلي، وRedis/KV في الإنتاج. عند حدوث إصابة تخزين مؤقت للمنتج، يكون زمن الاستجابة 5 مللي ثانية، وتنخفض استعلامات قاعدة البيانات بنسبة 95%.
3. بنية Nitro
(1) نظرة عامة على بنية Nitro
graph TB
A[Nitro Engine] --> B[Rollup Build]
A --> C[H3 HTTP Framework]
A --> D[Storage Abstraction]
A --> E[Cache System]
A --> F[Hook System]
A --> G[Multi-Preset Deploy]
B --> B1[Server bundle]
B --> B2[Tree-shaking unused code]
C --> C1[defineEventHandler]
C --> C2[Router / Middleware]
D --> D1[Memory Driver]
D --> D2[FileSystem Driver]
D --> D3[Redis Driver]
D --> D4[Cloud KV Driver]
E --> E1[cachedEventHandler]
E --> E2[defineCachedFunction]
G --> G1[Node Server]
G --> G2[Vercel / Cloudflare]
G --> G3[Docker / Lambda]
(2) مقارنة إعدادات Nitro
| الإعدادات | بيئة التشغيل | برنامج التخزين | طريقة النشر |
|---|---|---|---|
| node-server | Node.js | fs/memory/redis | PM2/Docker |
| Vercel | Vercel Serverless | Vercel KV | git push |
| vercel-edge | Vercel Edge | Edge KV | git push |
| cloudflare-pages | Cloudflare Workers | KV/R2 | wrangler deploy |
| netlify | Netlify Functions | Netlify Blobs | git push |
| bun | Bun runtime | fs/memory | bun run |
(1) ▶ مثال: تكوين إعداد Nitro
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
preset: process.env.DEPLOY_TARGET || 'node-server',
compressPublicAssets: true,
storage: {
// التطوير: برنامج الذاكرة
cache: { driver: 'memory' },
// الإنتاج: مكوّن عبر البيئة
products: { driver: process.env.STORAGE_DRIVER || 'memory' }
}
}
})
الناتج:
// التنفيذ ناجح
4. طبقة تجريد التخزين
(1) واجهة useStorage API
| الطريقة | الوصف | مثال |
|---|---|---|
| setItem(key, value) | كتابة | await storage.setItem('product:1', data) |
| getItem(key) | قراءة | await storage.getItem('product:1') |
| removeItem(key) | حذف | await storage.removeItem('product:1') |
| getKeys(base) | قائمة المفاتيح | await storage.getKeys('product:') |
| hasItem(key) | وجود | await storage.hasItem('product:1') |
| clear(base) | مسح | await storage.clear('product:') |
(1) ▶ مثال: تخزين مؤقت للمنتجات
// server/api/products/[id].get.ts - مع تخزين مؤقت
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id')
const storage = useStorage('products')
// محاولة التخزين المؤقت أولاً
const cached = await storage.getItem(`product:${id}`)
if (cached) return cached
// عدم الإصابة: جلب من قاعدة البيانات
const product = await fetchProductFromDB(Number(id))
if (!product) {
throw createError({ statusCode: 404, message: 'Product not found' })
}
// تخزين في التخزين المؤقت مع TTL (ساعة واحدة)
await storage.setItem(`product:${id}`, product, { ttl: 3600 })
return product
})
الناتج:
// التنفيذ ناجح
(2) ▶ مثال: تكوين تخزين Redis
// nuxt.config.ts - تخزين Redis للإنتاج
export default defineNuxtConfig({
nitro: {
storage: {
products: {
driver: 'redis',
url: process.env.REDIS_URL || 'redis://localhost:6379',
prefix: 'megashop:products:'
},
cache: {
driver: 'redis',
url: process.env.REDIS_URL || 'redis://localhost:6379',
prefix: 'megashop:cache:'
}
}
}
})
الناتج:
// التنفيذ ناجح
(2) مقارنة برامج التخزين
| البرنامج | السرعة | الديمومة | موزّع | حالات الاستخدام |
|---|---|---|---|---|
| memory | ⚡⚡⚡ | ❌ تُفقد عند إعادة التشغيل | ❌ | التطوير/الاختبار |
| fs | ⚡⚡ | ✅ محلي | ❌ | نشر مستقل |
| redis | ⚡⚡ | ✅ | ✅ | كتلة الإنتاج |
| cloudflare-kv | ⚡ | ✅ | ✅ | Cloudflare |
| vercel-kv | ⚡ | ✅ | ✅ | Vercel |
5. واجهة التخزين المؤقت
(1) ▶ مثال: cachedEventHandler
// server/api/products/featured.get.ts
export default cachedEventHandler(
async () => {
// نتيجة هذا المعالج مُخزّنة مؤقتًا
const products = await $fetch('/api/internal/products/featured')
return products
},
{
maxAge: 60 * 60, // تخزين مؤقت لساعة واحدة
swr: true, // تقديم القديم أثناء إعادة التحقق
staleMaxAge: 60 * 60 * 4, // صالح لـ 4 ساعات
getKey: () => 'featured-products',
varies: ['Accept-Language'] // تخزين مؤقت لكل لغة
}
)
الناتج:
// التنفيذ ناجح
(2) ▶ مثال: defineCachedFunction
// server/utils/cachedProduct.ts
export const getCachedProduct = defineCachedFunction(
async (id: number) => {
return await fetchProductFromDB(id)
},
{
maxAge: 60 * 60,
swr: true,
getKey: (id) => `product:${id}`,
name: 'cachedProduct'
}
)
// الاستخدام في معالج API
// server/api/products/[id].get.ts
export default defineEventHandler(async (event) => {
const id = Number(getRouterParam(event, 'id'))
return await getCachedProduct(id)
})
الناتج:
// التنفيذ ناجح
6. خطافات الأحداث
(1) خطافات دورة حياة Nitro
| الخطاف | شرط التفعيل | الغرض |
|---|---|---|
| close | إيقاف الخدمة | تحرير الموارد |
| error | خطأ غير معالج | الإبلاغ عن الأخطاء |
| request | بدء الطلب | تسجيل/مصادقة |
| response | إرسال الاستجابة | إحصائيات الأداء |
| beforeResponse | قبل الاستجابة | تعديل الاستجابة |
(1) ▶ مثال: خطاف حدث تحديث المخزون
// server/plugins/stock.ts
export default defineNitroPlugin((nitroApp) => {
// الاستماع لأحداث تحديث المخزون
nitroApp.hooks.hook('request', async (event) => {
const url = getRequestURL(event)
if (url.pathname === '/api/orders' && getMethod(event) === 'POST') {
// قبل الطلب: التحقق من توفر المخزون
const body = await readBody(event)
const outOfStock = await checkStockAvailability(body.items)
if (outOfStock.length > 0) {
throw createError({
statusCode: 400,
message: `Items out of stock: ${outOfStock.join(', ')}`
})
}
}
})
// بعد الطلب: إبطال التخزين المؤقت للمنتج
nitroApp.hooks.hook('afterResponse', async (event) => {
const url = getRequestURL(event)
if (url.pathname === '/api/orders' && getMethod(event) === 'POST') {
const body = await readBody(event)
const storage = useStorage('products')
// إبطال التخزين المؤقت للمنتجات
for (const item of body.items) {
await storage.removeItem(`product:${item.productId}`)
}
}
})
})
الناتج:
// التنفيذ ناجح
7. مثال شامل: نظام تخزين مؤقت لمنتجات MegaShop
// nuxt.config.ts - تكوين Nitro للإنتاج
export default defineNuxtConfig({
nitro: {
preset: process.env.DEPLOY_TARGET || 'node-server',
compressPublicAssets: true,
storage: {
products: {
driver: process.env.NODE_ENV === 'production' ? 'redis' : 'memory',
url: process.env.REDIS_URL
}
},
cache: {
pages: ['/products/', '/categories/']
}
}
})
// server/api/products/[id].get.ts - استراتيجية تخزين مؤقت كاملة
export default cachedEventHandler(
async (event) => {
const id = Number(getRouterParam(event, 'id'))
const product = await fetchProductFromDB(id)
if (!product) {
throw createError({ statusCode: 404, message: 'Product not found' })
}
return product
},
{
maxAge: 60 * 60, // تخزين مؤقت لساعة واحدة
swr: true, // تقديم القديم أثناء إعادة التحقق
staleMaxAge: 60 * 60 * 24, // صالح لـ 24 ساعة
getKey: (event) => `product:${getRouterParam(event, 'id')}`,
varies: ['Accept-Language']
}
)
// server/api/admin/invalidate-cache.post.ts - إبطال يدوي للتخزين المؤقت
export default defineEventHandler(async (event) => {
const { productId, scope } = await readBody(event)
const storage = useStorage('products')
if (scope === 'all') {
// إبطال جميع تخزينات المنتجات المؤقتة
const keys = await storage.getKeys()
for (const key of keys) {
await storage.removeItem(key)
}
return { message: 'All caches invalidated' }
}
if (productId) {
await storage.removeItem(`product:${productId}`)
return { message: `Cache invalidated for product ${productId}` }
}
throw createError({ statusCode: 400, message: 'Specify productId or scope=all' }
})
❓ أسئلة شائعة
useStorage؟nuxt.config.ts—لا حاجة لتغيير الكود.cachedEventHandler وrouteRules في SWR؟routeRules في SWR يخزن HTML الصفحة بالكامل مؤقتًا، بينما cachedEventHandler يخزن بيانات استجابة API مؤقتًا. استخدم routeRules على مستوى الصفحة وcachedEventHandler على مستوى API.plugins/) تعمل ضمن تطبيق Vue وتتعامل مع المكونات وComposables وSDKs الطرف الثالث.useStorage لتوفير تجريد موحد عبر بيئات متعددة.📖 ملخص
- Nitro هو محرك جانب الخادم لـ Nuxt 3: بناء + توجيه H3 + تخزين + تخزين مؤقت + إعدادات نشر متعددة
- useStorage() يوفر واجهة تخزين موحدة؛ استخدم الذاكرة للتطوير وRedis/KV للإنتاج، دون الحاجة لتغيير الكود
cachedEventHandlerيخزن استجابات API مؤقتًا؛defineCachedFunctionيخزن نتائج الدوال مؤقتًا- خطافات Nitro تراقب دورة حياة الطلب/الاستجابة لتنفيذ المصادقة والتسجيل وإبطال التخزين المؤقت
- يستخدم MegaShop تخزين Nitro لتخزين المنتجات مؤقتًا + خطافات أحداث لإبطال التخزين المؤقت تلقائيًا عند تحديث المخزون
📝 تمارين
- تمرين أساسي (الصعوبة: ⭐): تكوين برنامج ذاكرة useStorage لتنفيذ عمليات قراءة وكتابة بسيطة لتخزين مؤقت لبيانات المنتجات
- تمرين متقدم (الصعوبة: ⭐⭐): استخدام
cachedEventHandlerلتنفيذ تخزين مؤقت لواجهة قائمة المنتجات، والتحقق من أن الاستجابة أسرع عند إصابة التخزين المؤقت. - تحدي (الصعوبة: ⭐⭐⭐): تنفيذ آلية إبطال تخزين مؤقت كاملة—إبطال التخزين المؤقت للمنتجات ذات الصلة تلقائيًا عند إنشاء طلب؛ يجب أن تسمح واجهة API يدوية بمسح التخزين المؤقت حسب معرف المنتج أو مسح التخزين المؤقت بالكامل.
---|



