مسارات API من جهة الخادم
يحتاج Bob إلى إضافة واجهات برمجة تطبيقات خلفية إلى MegaShop—بما في ذلك عمليات CRUD للمنتجات ووظائف سلة التسوق ومصادقة المستخدمين. النهج التقليدي يتطلب نشر خادم Express منفصل، مما يجعل التكامل بين الواجهة الأمامية والخلفية كابوساً. اكتشف Charlie أن Nuxt 3 يتيح كتابة واجهات API مباشرة في مجلد server/، معالجاً كلاً من الواجهة الأمامية والخلفية ضمن مشروع واحد.
1. ما ستتعلمه
- تعريفات مسارات server/api/ وتعيينات طرق HTTP
- معالجة الأحداث: defineEventHandler / readBody / getQuery / getRouterParam
- الاعتراض العام في server/middleware/
- معالجة النطاق المشترك و CORS
- دليل عملي لواجهات API في MegaShop: /api/products / /api/cart / /api/auth
2. قصة حقيقية لمسؤول
(1) نقطة الألم: كابوس دمج أنظمة الواجهة الأمامية والخلفية المنفصلة
كتب Bob خادم API مستقلاً باستخدام Express، يعمل على المنفذ 4000. تعمل الواجهة الأمامية Nuxt على المنفذ 3000. أثناء التطوير، كان عليه تكوين وسيط معالجة الطلبات المشتركة والتعامل مع طلبات النطاق المشترك، ويتطلب النشر خطتي CI/CD منفصلتين. كانت طلبات سلة التسوق الخاصة بـ Alice تُحظر أحياناً بسبب مشاكل النطاق المشترك، مما أدى إلى أخطاء في الإنتاج.
(2) حل API خادم Nuxt
في Nuxt 3، يتيح لك مجلد server/ كتابة واجهات API مباشرة داخل المشروع؛ وبما أنها على نفس النطاق والمنفذ، فلا توجد مشاكل نطاق مشترك:
// server/api/products/index.get.ts
export default defineEventHandler(() => {
return { items: products, total: 1000000 }
})
(3) الفوائد: حل متكامل للكدس الكامل
يحتفظ Bob بمشروع واحد فقط، مع نشر API والواجهة الأمامية على نفس النطاق، لذا لن تواجه Alice أخطاء النطاق المشترك مرة أخرى، وتُبسَّط عملية النشر بنسبة 50%.
3. تعريفات مسارات API
(1) قواعد تعيين مسارات الملفات
| مسار الملف | طريقة HTTP | عنوان المسار |
|---|---|---|
| server/api/products.ts | ALL | /api/products |
| server/api/products/index.ts | ALL | /api/products |
| server/api/products/index.get.ts | GET | /api/products |
| server/api/products/index.post.ts | POST | /api/products |
| server/api/products/[id].get.ts | GET | /api/products/:id |
| server/api/products/[id].put.ts | PUT | /api/products/:id |
| server/api/products/[id].delete.ts | DELETE | /api/products/:id |
| server/api/auth/login.post.ts | POST | /api/auth/login |
(2) دورة حياة معالجة طلبات API
sequenceDiagram
participant C as Client
participant M as Server Middleware
participant H as Event Handler
participant D as Data Source
C->>M: HTTP Request
M->>M: Auth check / CORS / Logging
M->>H: Pass event
H->>D: Query / Mutation
D-->>H: Data result
H-->>M: HTTP Response
M-->>C: JSON Response
(1) ▶ مثال: GET قائمة المنتجات
// server/api/products/index.get.ts
export default defineEventHandler((event) => {
const query = getQuery(event)
const page = Number(query.page) || 1
const limit = Number(query.limit) || 20
const category = query.category as string
let filtered = mockProducts
if (category) {
filtered = filtered.filter(p => p.category === category)
}
const start = (page - 1) * limit
return {
items: filtered.slice(start, start + limit),
total: filtered.length,
page,
limit
}
})
الناتج:
// تم التنفيذ بنجاح
(2) ▶ مثال: POST لإنشاء منتج
// server/api/products/index.post.ts
export default defineEventHandler(async (event) => {
const body = await readBody(event)
// التحقق من الحقول المطلوبة
if (!body.name || !body.price) {
throw createError({
statusCode: 400,
message: 'Name and price are required'
})
}
const newProduct = {
id: mockProducts.length + 1,
name: body.name,
price: Number(body.price),
category: body.category || 'uncategorized',
inStock: body.inStock ?? true,
image: body.image || '/images/placeholder.webp'
}
mockProducts.push(newProduct)
return { product: newProduct, message: 'Product created successfully' }
})
الناتج:
// تم التنفيذ بنجاح
(3) ▶ مثال: مسار معلمات ديناميكية
// server/api/products/[id].get.ts
export default defineEventHandler((event) => {
const id = Number(getRouterParam(event, 'id'))
const product = mockProducts.find(p => p.id === id)
if (!product) {
throw createError({
statusCode: 404,
message: 'Product not found'
})
}
return product
})
الناتج:
// تم التنفيذ بنجاح
(4) ▶ مثال: PUT لتحديث منتج
// server/api/products/[id].put.ts
export default defineEventHandler(async (event) => {
const id = Number(getRouterParam(event, 'id'))
const body = await readBody(event)
const index = mockProducts.findIndex(p => p.id === id)
if (index === -1) {
throw createError({ statusCode: 404, message: 'Product not found' })
}
mockProducts[index] = { ...mockProducts[index], ...body }
return { product: mockProducts[index], message: 'Product updated' }
})
الناتج:
// تم التنفيذ بنجاح
(5) ▶ مثال: DELETE—حذف منتج
// server/api/products/[id].delete.ts
export default defineEventHandler((event) => {
const id = Number(getRouterParam(event, 'id'))
const index = mockProducts.findIndex(p => p.id === id)
if (index === -1) {
throw createError({ statusCode: 404, message: 'Product not found' })
}
const deleted = mockProducts.splice(index, 1)
return { product: deleted[0], message: 'Product deleted' }
})
الناتج:
// تم التنفيذ بنجاح
4. دوال مساعدة لمعالجة الأحداث
(1) مرجع سريع لأدوات الطلبات
| الدالة | الغرض | مثال |
|---|---|---|
| getQuery(event) | استرجاع معلمات استعلام URL | { page: '1', limit: '20' } |
| getRouterParam(event, key) | الحصول على معلمة المسار | /products/123 → '123' |
| readBody(event) | قراءة محتوى الطلب | { name: 'Product', price: 99 } |
| getHeader(event, key) | الحصول على رأس الطلب | 'Bearer token...' |
| getCookie(event, key) | الحصول على Cookie | 'session-id-xxx' |
| setCookie(event, key, val, opts) | تعيين cookie | setCookie(event, 'token', jwt, { httpOnly: true }) |
| setHeader(event, key, val) | تعيين رأس الاستجابة | setHeader(event, 'x-total', '1000') |
| createError(opts) | رمي خطأ | createError({ statusCode: 404 }) |
(2) اصطلاحات تنسيق الاستجابة
| السيناريو | رمز الحالة | محتوى الاستجابة |
|---|---|---|
| استعلام ناجح | 200 | { items: [], total: 0 } |
| إنشاء ناجح | 201 | { product: {}, message: '...' } |
| تحديث ناجح | 200 | { product: {}, message: '...' } |
| حذف ناجح | 200 | { message: '...' } |
| خطأ في المعلمات | 400 | { statusCode: 400, message: '...' } |
| غير مصادق | 401 | { statusCode: 401, message: '...' } |
| غير موجود | 404 | { statusCode: 404, message: '...' } |
5. وسيط الخادم
(1) ▶ مثال: وسيط تسجيل عام
// server/middleware/logger.ts
export default defineEventHandler((event) => {
const start = Date.now()
const method = getMethod(event)
const url = getRequestURL(event)
// تسجيل بعد الاستجابة
event.node.res.on('finish', () => {
const duration = Date.now() - start
const status = event.node.res.statusCode
console.log(`${method} ${url} → ${status} (${duration}ms)`)
})
})
الناتج:
// تم التنفيذ بنجاح
(2) ▶ مثال: وسيط المصادقة
// server/middleware/auth.ts
export default defineEventHandler((event) => {
const protectedPaths = ['/api/admin', '/api/orders', '/api/cart']
const url = getRequestURL(event)
const isProtected = protectedPaths.some(path => url.pathname.startsWith(path))
if (!isProtected) return
const token = getHeader(event, 'authorization')?.replace('Bearer ', '')
if (!token) {
throw createError({ statusCode: 401, message: 'Authentication required' })
}
// التحقق من رمز JWT (مبسط)
try {
const payload = verifyToken(token)
event.context.user = payload
} catch {
throw createError({ statusCode: 401, message: 'Invalid token' })
}
})
الناتج:
// تم التنفيذ بنجاح
6. معالجة CORS للنطاق المشترك
(1) ▶ مثال: تكوين CORS المدمج في Nitro
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
'/api/**': { cors: true }
}
})
الناتج:
// تم التنفيذ بنجاح
(2) ▶ مثال: وسيط CORS مخصص
// server/middleware/cors.ts
export default defineEventHandler((event) => {
const origin = getHeader(event, 'origin') || '*'
setHeader(event, 'Access-Control-Allow-Origin', origin)
setHeader(event, 'Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
setHeader(event, 'Access-Control-Allow-Headers', 'Content-Type, Authorization')
setHeader(event, 'Access-Control-Max-Age', '86400')
// معالجة الطلب المسبق
if (getMethod(event) === 'OPTIONS') {
event.node.res.statusCode = 204
return ''
}
})
الناتج:
// تم التنفيذ بنجاح
(1) مقارنة حلول CORS
| الحل | طريقة التكوين | المرونة | حالات الاستخدام |
|---|---|---|---|
| routeRules cors: true | nuxt.config.ts | منخفضة (تشغيل/إيقاف كلي) | بيئة التطوير |
| وسيط مخصص | server/middleware/ | عالية (دقيقة) | الإنتاج |
| Nitro routeRules متقدم | routeRules لكل مسار | متوسطة | متطلبات مختلطة |
7. مثال شامل: إطار عمل MegaShop API
// server/api/cart/index.get.ts - الحصول على عناصر سلة التسوق
export default defineEventHandler((event) => {
const userId = event.context.user?.id
if (!userId) {
throw createError({ statusCode: 401, message: 'Login required' })
}
const cart = mockCarts.find(c => c.userId === userId)
return cart || { userId, items: [], total: 0 }
})
// server/api/cart/index.post.ts - إضافة عنصر إلى سلة التسوق
export default defineEventHandler(async (event) => {
const userId = event.context.user?.id
const { productId, quantity = 1 } = await readBody(event)
const product = mockProducts.find(p => p.id === productId)
if (!product) {
throw createError({ statusCode: 404, message: 'Product not found' })
}
if (!product.inStock) {
throw createError({ statusCode: 400, message: 'Product out of stock' })
}
// إضافة أو تحديث عنصر في سلة التسوق
let cart = mockCarts.find(c => c.userId === userId)
if (!cart) {
cart = { userId, items: [], total: 0 }
mockCarts.push(cart)
}
const existing = cart.items.find(i => i.productId === productId)
if (existing) {
existing.quantity += quantity
} else {
cart.items.push({ productId, name: product.name, price: product.price, quantity })
}
cart.total = cart.items.reduce((sum, i) => sum + i.price * i.quantity, 0)
return { cart, message: 'Item added to cart' }
})
❓ أسئلة شائعة
server/ يُضمَّن في حزمة العميل؟server/ بشكل منفصل، لذا لا يتسرب إلى العميل. يمكن لكود الخادم استخدام واجهات برمجة Node.js والأسرار بأمان.createError و throw new Error؟createError مقدمة من H3 وتُرجع رمز حالة HTTP الصحيح واستجابة JSON. throw new Error يُرجع خطأ داخلي 500. نوصي باستخدام createError في مسارات API.server/middleware/ ويعترض طلبات API (المصادقة، التسجيل، CORS). وسيط العميل موجود في مجلد middleware/ ويعترض انتقالات مسارات الصفحات (حراس الصلاحيات/إعادة التوجيه).runtimeConfig في API؟useRuntimeConfig() لاسترجاعها؛ المتغيرات الخاصة متاحة فقط على الخادم: const config = useRuntimeConfig() → config.databaseUrl.event.node.res.write() وتعيين Content-Type: text/event-stream لتنفيذ SSE. يدعم Nitro واجهة برمجة استجابة Node.js الأصلية بالكامل.📖 ملخص
- مجلد
server/في Nuxt 3 هو واجهة API: مسارات الملفات تُعيَّن للمسارات، ولاحقات الطرق تُعيَّن لطرق HTTP - defineEventHandler + readBody/getQuery/getRouterParam لمعالجة الطلب
- وسيط الخادم كمعترض عام: التسجيل، المصادقة، CORS
- النشر ضمن نفس النطاق يتجنب مشاكل النطاق المشترك بشكل طبيعي؛ إذا لزم الأمر، يمكنك استخدام
routeRules corsأو وسيط مخصص - MegaShop يبني خلفية كاملة باستخدام /api/products و /api/cart و /api/auth
📝 تمارين
- تمرين أساسي (الصعوبة: ⭐): أنشئ نقاط النهاية GET /api/products و GET /api/products/[id]، والتي تُرجع بيانات منتجات وهمية.
- تمرين متقدم (الصعوبة: ⭐⭐): نفِّذ واجهة CRUD API كاملة (GET/POST/PUT/DELETE)، بما في ذلك التحقق من المعلمات ومعالجة الأخطاء.
- تحدٍ (الصعوبة: ⭐⭐⭐): نفِّذ المصادقة باستخدام وسيط من جهة الخادم. أرجئ خطأ 401 عندما يصل مستخدم غير مصادق إلى
/api/cart؛ اسمح بالعمليات الطبيعية بعد تسجيل الدخول.
---|



