404 Not Found

404 Not Found


nginx

مسارات API من جهة الخادم

يحتاج Bob إلى إضافة واجهات برمجة تطبيقات خلفية إلى MegaShop—بما في ذلك عمليات CRUD للمنتجات ووظائف سلة التسوق ومصادقة المستخدمين. النهج التقليدي يتطلب نشر خادم Express منفصل، مما يجعل التكامل بين الواجهة الأمامية والخلفية كابوساً. اكتشف Charlie أن Nuxt 3 يتيح كتابة واجهات API مباشرة في مجلد server/، معالجاً كلاً من الواجهة الأمامية والخلفية ضمن مشروع واحد.

1. ما ستتعلمه


2. قصة حقيقية لمسؤول

(1) نقطة الألم: كابوس دمج أنظمة الواجهة الأمامية والخلفية المنفصلة

كتب Bob خادم API مستقلاً باستخدام Express، يعمل على المنفذ 4000. تعمل الواجهة الأمامية Nuxt على المنفذ 3000. أثناء التطوير، كان عليه تكوين وسيط معالجة الطلبات المشتركة والتعامل مع طلبات النطاق المشترك، ويتطلب النشر خطتي CI/CD منفصلتين. كانت طلبات سلة التسوق الخاصة بـ Alice تُحظر أحياناً بسبب مشاكل النطاق المشترك، مما أدى إلى أخطاء في الإنتاج.

(2) حل API خادم Nuxt

في Nuxt 3، يتيح لك مجلد server/ كتابة واجهات API مباشرة داخل المشروع؛ وبما أنها على نفس النطاق والمنفذ، فلا توجد مشاكل نطاق مشترك:

TYPESCRIPT
// 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

100%
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 قائمة المنتجات

TYPESCRIPT
// 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
  }
})

الناتج:

TEXT
// تم التنفيذ بنجاح

(2) ▶ مثال: POST لإنشاء منتج

TYPESCRIPT
// 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' }
})

الناتج:

TEXT
// تم التنفيذ بنجاح

(3) ▶ مثال: مسار معلمات ديناميكية

TYPESCRIPT
// 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
})

الناتج:

TEXT
// تم التنفيذ بنجاح

(4) ▶ مثال: PUT لتحديث منتج

TYPESCRIPT
// 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' }
})

الناتج:

TEXT
// تم التنفيذ بنجاح

(5) ▶ مثال: DELETE—حذف منتج

TYPESCRIPT
// 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' }
})

الناتج:

TEXT
// تم التنفيذ بنجاح

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) ▶ مثال: وسيط تسجيل عام

TYPESCRIPT
// 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)`)
  })
})

الناتج:

TEXT
// تم التنفيذ بنجاح

(2) ▶ مثال: وسيط المصادقة

TYPESCRIPT
// 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' })
  }
})

الناتج:

TEXT
// تم التنفيذ بنجاح

6. معالجة CORS للنطاق المشترك

(1) ▶ مثال: تكوين CORS المدمج في Nitro

TYPESCRIPT
// nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    '/api/**': { cors: true }
  }
})

الناتج:

TEXT
// تم التنفيذ بنجاح

(2) ▶ مثال: وسيط CORS مخصص

TYPESCRIPT
// 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 ''
  }
})

الناتج:

TEXT
// تم التنفيذ بنجاح

(1) مقارنة حلول CORS

الحل طريقة التكوين المرونة حالات الاستخدام
routeRules cors: true nuxt.config.ts منخفضة (تشغيل/إيقاف كلي) بيئة التطوير
وسيط مخصص server/middleware/ عالية (دقيقة) الإنتاج
Nitro routeRules متقدم routeRules لكل مسار متوسطة متطلبات مختلطة

7. مثال شامل: إطار عمل MegaShop API

TYPESCRIPT
// 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 }
})
TYPESCRIPT
// 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/ يُضمَّن في حزمة العميل؟
ج لا. يقوم Nitro بتجميع كود server/ بشكل منفصل، لذا لا يتسرب إلى العميل. يمكن لكود الخادم استخدام واجهات برمجة Node.js والأسرار بأمان.
س هل اللاحقتان .get و .post مطلوبتان في أسماء ملفات مسارات API؟
ج لا، ليستا مطلوبتين. الملفات بدون لاحقة طريقة تعالج جميع طرق HTTP. إضافة اللاحقات يتيح تحكماً دقيقاً في منطق المعالجة لكل طريقة، لذا يُنصح بها.
س ما الفرق بين 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.
س هل يمكن لـ API إرجاع استجابة بث (SSE)؟
ج نعم. يمكنك كتابة البيانات في أجزاء باستخدام event.node.res.write() وتعيين Content-Type: text/event-stream لتنفيذ SSE. يدعم Nitro واجهة برمجة استجابة Node.js الأصلية بالكامل.

📖 ملخص


📝 تمارين

  1. تمرين أساسي (الصعوبة: ⭐): أنشئ نقاط النهاية GET /api/products و GET /api/products/[id]، والتي تُرجع بيانات منتجات وهمية.
  2. تمرين متقدم (الصعوبة: ⭐⭐): نفِّذ واجهة CRUD API كاملة (GET/POST/PUT/DELETE)، بما في ذلك التحقق من المعلمات ومعالجة الأخطاء.
  3. تحدٍ (الصعوبة: ⭐⭐⭐): نفِّذ المصادقة باستخدام وسيط من جهة الخادم. أرجئ خطأ 401 عندما يصل مستخدم غير مصادق إلى /api/cart؛ اسمح بالعمليات الطبيعية بعد تسجيل الدخول.

---|

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%