Node.js: تصميم REST API

آخر تحديث: 2026-08-26

يعمل فريق أليس على تطوير كل من الواجهة الأمامية والواجهة الخلفية لنظام إدارة المهام. ويشكو مطورو الواجهة الأمامية من أنهم لا يعرفون أي واجهات برمجة التطبيقات (APIs) متاحة، أو أي طرق HTTP يجب استخدامها، أو ما هي التنسيقات التي ستكون عليها الردود. ويشعر مطورو الواجهة الخلفية بنفس القدر من الإحباط — ففي عملية «تحديث المهمة» نفسها، يستخدم البعض طريقة POST، والبعض الآخر طريقة PUT، والبعض الآخر طريقة PATCH، مما يؤدي إلى تنوع كبير في تنسيقات الردود. وقد تحول التعاون إلى فوضى عارمة.

قررت أليس اعتماد مواصفات REST. وبعد أن قام الفريق بتوحيد تسمية الموارد، وتعيين الطرق، ورموز الحالة، وتنسيقات الاستجابات، أصبحت واجهة برمجة التطبيقات (API) واضحة ويمكن التنبؤ بها؛ ولم يعد فريق الواجهة الأمامية مضطرًا إلى مراجعة وثائق واجهة برمجة التطبيقات (API) مرارًا وتكرارًا، وتضاعفت كفاءة التعاون.

1. ما ستتعلمه



2. مبادئ بنية REST

(1) ما هو REST؟

REST (نقل الحالة التمثيلية) هو أسلوب هندسي برمجي اقترحه روي فيلدينغ في عام 2000. وهو يحدد مجموعة من القيود لتصميم واجهات تطبيقات الويب. ولا يُعد REST بروتوكولاً أو معياراً، بل هو فلسفة تصميمية.

(2) أربعة مبادئ أساسية

المبدأ المعنى مثال
المورد كل شيء يُعد موردًا، ويُحدد بواسطة عنوان URL /tasks، /users/42
طبقة التمثيل التنسيق الذي يتم به تمثيل المورد، مثل JSON {"id": 1, "タイトル": "Learn REST"}
بدون حالة يحتوي كل طلب على جميع المعلومات الضرورية تتضمن الطلبات رمزًا مميزًا ولا تعتمد على الجلسات
واجهة موحدة التعامل مع الموارد باستخدام طرق HTTP القياسية GET للقراءة، وPOST للإنشاء، وDELETE للحذف

▶ مثال: بدون حالة مقابل مع حالة

JAVASCRIPT
// Stateful: Depends on server session (not RESTful)
app.post('/login', (req, res) => {
  req.session.userId = 42; // Server State Saving
  res.send('logged in');
});

app.get('/profile', (req, res) => {
  const userId = req.session.userId; // Depends on the server status
  res.json({ id: userId, name: 'Alice' });
});

// Stateless:Include authentication information with every request(RESTful)
app.get('/profile', (req, res) => {
  const userId = verifyToken(req.headers.authorization);
  res.json({ id: userId, name: 'Alice' });
});
▶ جرّب الكود

3. عمليات CRUD وتعيينات طرق HTTP

(1) علاقة التعيين القياسية

تتمثل الفكرة الأساسية لـ REST في استخدام طرق HTTP للتعبير عن الغرض من العمليات التي تُجرى على الموارد، بدلاً من تضمين أفعال في عناوين URL.

عمليات CRUD طرق HTTP المسارات القابلية للتكرار الأمان
إنشاء نشر /tasks لا لا
قراءة (قائمة) GET /tasks نعم نعم
قراءة (مرة واحدة) GET /tasks/42 نعم نعم
تحديث (كامل) PUT /tasks/42 نعم لا
تحديث (جزئي) تصحيح /tasks/42 لا لا
حذف حذف /tasks/42 نعم لا

(2) شرح مفصل لمفهوم الإيدمبوتينسية

القدرة على التكرار تعني أن تنفيذ الطلب نفسه مرة واحدة له نفس تأثير تنفيذه عدة مرات. تعتبر طلبات GET وPUT وDELETE قابلة للتكرار، في حين أن طلبات POST وPATCH ليست كذلك.

▶ مثال: الاختلافات في خاصية الإيدمبوتنتية بين PUT و POST

JAVASCRIPT
// POST: Creates a new resource on every call (Non-idempotent)
// 1st POST /tasks → Create id=1
// 2nd POST /tasks → Create id=2
app.post('/tasks', (req, res) => {
  const task = { id: nextId++, ...req.body };
  tasks.push(task);
  res.status(201).json(task);
});

// PUT: Replaces the same resource on every call (Idempotent)
// 1st PUT /tasks/1 → Replace id=1
// 2nd PUT /tasks/1 → Replace id=1 (The results are the same)
app.put('/tasks/:id', (req, res) => {
  const idx = tasks.findIndex(t => t.id === parseInt(req.params.id));
  if (idx === -1) return res.status(404).json({ error: 'Not found' });
  tasks[idx] = { id: parseInt(req.params.id), ...req.body };
  res.json(tasks[idx]);
});
▶ جرّب الكود

▶ مثال: التحديث الجزئي باستخدام PATCH

JAVASCRIPT
// PATCH:Modify only the fields provided
app.patch('/tasks/:id', (req, res) => {
  const task = tasks.find(t => t.id === parseInt(req.params.id));
  if (!task) return res.status(404).json({ error: 'Not found' });
  Object.assign(task, req.body);
  res.json(task);
});

// Request:Modify only status Field
// PATCH /tasks/1  {"status": "done"}
// Raw Data:{"id":1,"title":"Learn REST","status":"pending"}
// Results:{"id":1,"title":"Learn REST","status":"done"}
▶ جرّب الكود

4. إرشادات تصميم عناوين URL

(1) القواعد الأساسية

يتبع تصميم عناوين URL وفقًا لمعايير REST مجموعة من القواعد التي تجعل واجهات برمجة التطبيقات (APIs) بديهية وسهلة القراءة.

القاعدة صحيح ✅ غير صحيح ❌
استخدم الأسماء، لا الأفعال GET /tasks GET /getTasks
استخدم صيغة الجمع، لا صيغة المفرد /tasks /task
تمثيل العلاقات باستخدام التداخل /users/42/tasks /tasksByUser?userId=42
لا يزيد عن 3 مستويات /users/42/tasks/1 /orgs/1/teams/2/users/42/tasks
التصفية حسب معلمات الاستعلام /tasks?status=done /doneTasks
باستخدام غلاف الكباب /task-アイテム /taskItems

(2) تصميم الموارد المتداخلة

تشير الموارد المتداخلة إلى وجود علاقة هرمية. استخدم المسارات المتداخلة عندما لا يمكن للمورد الفرعي أن يوجد بشكل مستقل عن المورد الأصلي.

▶ مثال: تصميم عناوين URL لنظام إدارة المهام

TEXT 📖 للعرض فقط
# Mission Resources
GET    /tasks              # Get the task list
POST   /tasks              # Create a New Task
GET    /tasks/42           # Get a Single Task
PUT    /tasks/42           # Full Update Task
PATCH  /tasks/42           # Partial Update Task
DELETE /tasks/42           # Delete Task

# Comments on the Assignment(Nested Resources)
GET    /tasks/42/comments           # Get a Task42List of comments
POST   /tasks/42/comments           # For the mission42Add a comment
GET    /tasks/42/comments/7         # Get a Task42Comments on7
DELETE /tasks/42/comments/7         # Delete Comment7

# Filtering and Pagination
GET    /tasks?status=done&page=2&limit=20
GET    /tasks?sort=-created_at      # Sort by creation date in reverse chronological order

▶ مثال: الاستخدامات الشائعة لمعلمات الاستعلام في عناوين URL

JAVASCRIPT
app.get('/tasks', (req, res) => {
  let result = [...tasks];

  // Filter
  if (req.query.status) {
    result = result.filter(t => t.status === req.query.status);
  }

  // Sort
  if (req.query.sort) {
    const field = req.query.sort.startsWith('-')
      ? req.query.sort.slice(1)
      : req.query.sort;
    const order = req.query.sort.startsWith('-') ? -1 : 1;
    result.sort((a, b) => (a[field] > b[field] ? order : -order));
  }

  // Pagination
  const page = parseInt(req.query.page) || 1;
  const limit = parseInt(req.query.limit) || 20;
  const start = (page - 1) * limit;
  result = result.slice(start, start + limit);

  res.json({
    data: result,
    page,
    limit,
    total: tasks.length
  });
});
▶ جرّب الكود

5. اختيار رموز حالة HTTP

(1) تصنيف رموز الحالة واختيارها

تعد رموز حالة HTTP إشارات أساسية للتواصل بين واجهات برمجة التطبيقات (API) التي تعمل بنظام REST والعملاء. ومن خلال اختيار رمز الحالة الصحيح، يمكن للعملاء فهم نتيجة الطلب بدقة.

السيناريو رمز الحالة المعنى الوصف
تم استرداد المورد بنجاح 200 OK الطلب ناجح تُرجع هذه الرسالة عند نجاح عمليات GET/PUT/PATCH
تم إنشاء المورد بنجاح تم إنشاء 201 تم إنشاء المورد يتم إرجاعها عند نجاح طلب POST؛ ويجب أن تتضمن رأس «Location»
تم حذف المورد بنجاح 204 لا يوجد محتوى لا يوجد محتوى تُرجع عند نجاح أمر DELETE؛ لا يوجد نص استجابة
معلمات طلب غير صالحة 400 طلب غير صحيح خطأ في صيغة طلب العميل الحقول الإلزامية مفقودة، أو التنسيق غير صحيح
لم تتم المصادقة 401 غير مصرح به لم يتم توفير معلومات المصادقة الرمز مفقود أو غير صالح
لا يوجد إذن 403 ممنوع تم المصادقة ولكن لا يوجد إذن وصول مستخدم عادي إلى واجهة الإدارة
المورد غير موجود 404 غير موجود المورد المطلوب غير موجود لم يتم العثور على المورد الذي يحمل هذا المعرّف
خطأ في الخادم 500 خطأ داخلي في الخادم خطأ داخلي في الخادم استثناء لم يتم التعامل معه

(2) الأخطاء الشائعة: الاستخدام غير الصحيح لرموز الحالة

▶ مثال: الاستخدام الصحيح لرموز الحالة

JAVASCRIPT
// Create a Resource → 201 + Location
app.post('/tasks', (req, res) => {
  const task = { id: nextId++, ...req.body };
  tasks.push(task);
  res.status(201)
     .location(`/tasks/${task.id}`)
     .json(task);
});

// Delete Resource → 204(Non-responsive body)
app.delete('/tasks/:id', (req, res) => {
  const idx = tasks.findIndex(t => t.id === parseInt(req.params.id));
  if (idx === -1) return res.status(404).json({ error: 'Not found' });
  tasks.splice(idx, 1);
  res.status(204).end();
});

// Verification Failed → 400 + Error Details
app.post('/tasks', (req, res) => {
  if (!req.body.title) {
    return res.status(400).json({
      error: 'Validation failed',
      details: [{ field: 'title', message: 'Title is required' }]
    });
  }
  // ...
});
▶ جرّب الكود

6. تنسيقات الطلبات والردود

(1) قواعد مواصفات JSON

الاتفاقية المعيار المثال
تسمية الحقول camelCase createdAt، taskId
تنسيق التاريخ ISO 8601 2025-07-03T10:30:00Z
قائمة الردود تتضمن البيانات + معلومات ترقيم الصفحات {"data": [...], "total": 100}
استجابة الخطأ تتضمن الخطأ + التفاصيل {"error": "Not found", "details": [...]}
التعامل مع القيم الفارغة استخدم null بدلاً من حذف الحقل {"description": null}
نوع المعرّف سلسلة (لتجنب مشاكل الدقة) {"id": "42"}

▶ مثال: رد قائمة موحد

JAVASCRIPT
app.get('/tasks', (req, res) => {
  const page = parseInt(req.query.page) || 1;
  const limit = parseInt(req.query.limit) || 20;
  const start = (page - 1) * limit;
  const data = tasks.slice(start, start + limit);

  res.json({
    data,
    pagination: {
      page,
      limit,
      total: tasks.length,
      totalPages: Math.ceil(tasks.length / limit)
    }
  });
});
▶ جرّب الكود
TEXT 📖 للعرض فقط
// Response Example
{
  "data": [
    {
      "id": "1",
      "タイトル": "Learn REST",
      "status": "pending",
      "createdAt": "2025-07-03T10:30:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 1,
    "totalPages": 1
  }
}

▶ مثال: الاستجابة القياسية للخطأ

JAVASCRIPT
// Unified Error Handling Middleware
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(err.status || 500).json({
    error: err.message || 'Internal Server Error',
    details: err.details || [],
    requestId: req.id,
    timestamp: new Date().toISOString()
  });
});

// Custom Error Classes
class ApiError extends Error {
  constructor(status, message, details = []) {
    super(message);
    this.status = status;
    this.details = details;
  }
}

// Usage
app.get('/tasks/:id', (req, res, next) => {
  const task = tasks.find(t => t.id === parseInt(req.params.id));
  if (!task) {
    return next(new ApiError(404, 'Task not found', [
      { field: 'id', message: `No task with id ${req.params.id}` }
    ]));
  }
  res.json(task);
});
▶ جرّب الكود
TEXT 📖 للعرض فقط
// Examples of Error Responses
{
  "error": "Task not found",
  "details": [
    { "field": "id", "message": "No task with id 999" }
  ],
  "requestId": "req-a1b2c3",
  "timestamp": "2025-07-03T10:30:00Z"
}


7. استراتيجية تحديد إصدارات واجهة برمجة التطبيقات (API)

(1) مقارنة بين ثلاث استراتيجيات سائدة

الاستراتيجية مثال المزايا العيوب السيناريوهات التي يمكن تطبيقها
URL Path /api/v1/tasks Intuitive, can be tested in a browser Longer URLs, controversial among purist REST advocates Most public APIs
رأس الطلب Accept: application/vnd.myapi.v1+json عنوان URL نظيف ونقي يتوافق مع معايير REST غير بديهي، ويصعب تصحيح أخطائه يسعى إلى الحفاظ على نقاء REST
معلمة الاستعلام /api/tasks?version=1 أبسط يسهل إغفالها؛ استراتيجية تخزين مؤقت معقدة واجهات برمجة التطبيقات الداخلية، المشاريع البسيطة

(2) أفضل الممارسات في إدارة الإصدارات

▶ مثال: تطبيق نظام إصدارات مسارات عناوين URL

JAVASCRIPT
// Routing Structure
// /api/v1/tasks → v1 Logic
// /api/v2/tasks → v2 Logic

const express = require('express');
const app = express();

// v1 Routing
const v1Router = express.Router();
v1Router.get('/tasks', (req, res) => {
  res.json({ data: tasks, version: 'v1' }); // v1 Return Format
});

// v2 Routing(Response Format Upgrade)
const v2Router = express.Router();
v2Router.get('/tasks', (req, res) => {
  res.json({                        // v2 Return Format(Includes pagination)
    data: tasks,
    pagination: { page: 1, total: tasks.length }
  });
});

app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router);

// Response Header Version
app.use('/api/v2', (req, res, next) => {
  res.setHeader('X-API-Version', '2.0');
  next();
});
▶ جرّب الكود

8. نموذج نضج REST

(1) نموذج ريتشاردسون للنضج

اقترح ليونارد ريتشاردسون نموذجًا لقياس درجة نضج واجهة برمجة التطبيقات (API) وفقًا لمعايير RESTful:

100%
graph TD
    L0["Level 0: Single endpoint<br/>HTTP as a tunnel<br/>e.g. POST /api  {action: getTasks}"]
    L1["Level 1: Resource URLs<br/>One URL per resource<br/>e.g. POST /tasks, POST /users"]
    L2["Level 2: HTTP Methods<br/>GET/POST/PUT/DELETE<br/>e.g. GET /tasks, DELETE /tasks/1"]
    L3["Level 3: HATEOAS<br/>Responses contain hyperlinks<br/>e.g. Response includes next, self links"]
    L0 --> L1 --> L2 --> L3
    style L0 fill:#ff6b6b,color:#fff
    style L1 fill:#ffa502,color:#fff
    style L2 fill:#2ed573,color:#fff
    style L3 fill:#1e90ff,color:#fff
المستوى الخصائص طلب نموذجي استجابة نموذجية
المستوى 0 نفق HTTP، عنوان URL واحد POST /api {"action":"getTasks"} {"tasks": [...]}
المستوى 1 فصل الموارد؛ يُسمح بأي طريقة POST /tasks {"tasks": [...]}
المستوى 2 HTTP الصحيح من الناحية الدلالية GET /tasks 200 {"data": [...]}
المستوى 3 HATEOAS Hypermedia GET /tasks/1 يتضمن _links التنقل

(2) شرح مفصل لنظام HATEOAS

تتطلب معيار HATEOAS (الوسائط الفائقة كمحرك لحالة التطبيق) أن تتضمن الردود روابط إلى العمليات ذات الصلة، بحيث لا يضطر العملاء إلى تضمين عناوين URL بشكل ثابت في الكود.

▶ مثال: المستوى 3 — الرد برابط HATEOAS

JAVASCRIPT
app.get('/tasks/:id', (req, res) => {
  const task = tasks.find(t => t.id === parseInt(req.params.id));
  if (!task) return res.status(404).json({ error: 'Not found' });

  res.json({
    ...task,
    _links: {
      self: { href: `/tasks/${task.id}`, method: 'GET' },
      update: { href: `/tasks/${task.id}`, method: 'PUT' },
      delete: { href: `/tasks/${task.id}`, method: 'DELETE' },
      assign: { href: `/tasks/${task.id}/assignee`, method: 'POST' },
      comments: { href: `/tasks/${task.id}/comments`, method: 'GET' }
    }
  });
});
▶ جرّب الكود
TEXT 📖 للعرض فقط
// Response
{
  "id": "1",
  "タイトル": "Learn REST",
  "status": "pending",
  "createdAt": "2025-07-03T10:30:00Z",
  "_links": {
    "self": { "href": "/tasks/1", "method": "GET" },
    "update": { "href": "/tasks/1", "method": "PUT" },
    "حذف": { "href": "/tasks/1", "method": "DELETE" },
    "assign": { "href": "/tasks/1/assignee", "method": "POST" },
    "comments": { "href": "/tasks/1/comments", "method": "GET" }
  }
}


9. مثال شامل: التصميم الكامل لواجهة برمجة تطبيقات إدارة المهام

صمم فريق أليس واجهة برمجة تطبيقات (API) كاملة تعتمد على نمط RESTful لنظام إدارة المهام، تغطي كل شيء بدءًا من تعريفات الموارد وصولاً إلى معالجة الأخطاء.

▶ مثال: واجهة برمجة تطبيقات إدارة المهام الكاملة

(1) تعريف المورد

المورد المسار الوصف
مجموعة المهام /api/v1/tasks جميع المهام
مهمة فردية /api/v1/tasks/:id مهمة محددة
تعليقات على المهمة /api/v1/tasks/:id/comments تعليقات على مهمة معينة
علامة المهمة /api/v1/tasks/:id/tags علامة لمهمة محددة

(2) تعيين الطرق والطلبات/الاستجابات

JAVASCRIPT 📖 للعرض فقط
const express = require('express');
const app = express();
app.use(express.json());

let tasks = [
  { id: 1, title: 'Design database schema', status: 'done', priority: 'high', createdAt: '2025-07-01T08:00:00Z' },
  { id: 2, title: 'Implement REST API', status: 'in-progress', priority: 'high', createdAt: '2025-07-02T09:00:00Z' }
];
let nextId = 3;

// GET /api/v1/tasks — Get the task list
app.get('/api/v1/tasks', (req, res) => {
  const { status, priority, page = 1, limit = 20 } = req.query;
  let result = [...tasks];
  if (status) result = result.filter(t => t.status === status);
  if (priority) result = result.filter(t => t.priority === priority);

  const start = (page - 1) * limit;
  const data = result.slice(start, start + Number(limit));

  res.json({
    data,
    pagination: {
      page: Number(page),
      limit: Number(limit),
      total: result.length,
      totalPages: Math.ceil(result.length / Number(limit))
    }
  });
});

// GET /api/v1/tasks/:id — Get a Single Task
app.get('/api/v1/tasks/:id', (req, res) => {
  const task = tasks.find(t => t.id === parseInt(req.params.id));
  if (!task) {
    return res.status(404).json({
      error: 'Task not found',
      details: [{ field: 'id', message: `No task with id ${req.params.id}` }],
      timestamp: new Date().toISOString()
    });
  }
  res.json({
    data: task,
    _links: {
      self: { href: `/api/v1/tasks/${task.id}` },
      update: { href: `/api/v1/tasks/${task.id}`, method: 'PUT' },
      delete: { href: `/api/v1/tasks/${task.id}`, method: 'DELETE' },
      comments: { href: `/api/v1/tasks/${task.id}/comments` }
    }
  });
});

// POST /api/v1/tasks — Create a Task
app.post('/api/v1/tasks', (req, res) => {
  const { title, priority } = req.body;
  if (!title) {
    return res.status(400).json({
      error: 'Validation failed',
      details: [{ field: 'title', message: 'Title is required' }],
      timestamp: new Date().toISOString()
    });
  }
  const task = {
    id: nextId++,
    title,
    status: 'pending',
    priority: priority || 'medium',
    createdAt: new Date().toISOString()
  };
  tasks.push(task);
  res.status(201).location(`/api/v1/tasks/${task.id}`).json({ data: task });
});

// PUT /api/v1/tasks/:id — Full Update
app.put('/api/v1/tasks/:id', (req, res) => {
  const idx = tasks.findIndex(t => t.id === parseInt(req.params.id));
  if (idx === -1) {
    return res.status(404).json({
      error: 'Task not found',
      details: [{ field: 'id', message: `No task with id ${req.params.id}` }],
      timestamp: new Date().toISOString()
    });
  }
  const { title, status, priority } = req.body;
  if (!title || !status) {
    return res.status(400).json({
      error: 'Validation failed',
      details: [
        ...(!title ? [{ field: 'title', message: 'Title is required' }] : []),
        ...(!status ? [{ field: 'status', message: 'Status is required' }] : [])
      ],
      timestamp: new Date().toISOString()
    });
  }
  tasks[idx] = { id: tasks[idx].id, title, status, priority: priority || 'medium', createdAt: tasks[idx].createdAt };
  res.json({ data: tasks[idx] });
});

// PATCH /api/v1/tasks/:id — Partial Update
app.patch('/api/v1/tasks/:id', (req, res) => {
  const task = tasks.find(t => t.id === parseInt(req.params.id));
  if (!task) {
    return res.status(404).json({
      error: 'Task not found',
      details: [{ field: 'id', message: `No task with id ${req.params.id}` }],
      timestamp: new Date().toISOString()
    });
  }
  Object.assign(task, req.body);
  res.json({ data: task });
});

// DELETE /api/v1/tasks/:id — Delete Task
app.delete('/api/v1/tasks/:id', (req, res) => {
  const idx = tasks.findIndex(t => t.id === parseInt(req.params.id));
  if (idx === -1) {
    return res.status(404).json({
      error: 'Task not found',
      details: [{ field: 'id', message: `No task with id ${req.params.id}` }],
      timestamp: new Date().toISOString()
    });
  }
  tasks.splice(idx, 1);
  res.status(204).end();
});

app.listen(3000, () => console.log('Task API running on port 3000'));
111 سطر من الكود المنطقي (تجاوز الحد 40, للعرض فقط)

(3) مرجع سريع للطلبات والردود

BASH
# Create a Task
curl -X POST http://localhost:3000/api/v1/tasks \
  -H "Content-Type: application/json" \
  -d '{"title":"Write documentation","priority":"high"}'

# Get the task list(Filter + Pagination)
curl http://localhost:3000/api/v1/tasks?status=pending&page=1&limit=10

# Partial Update
curl -X PATCH http://localhost:3000/api/v1/tasks/1 \
  -H "Content-Type: application/json" \
  -d '{"status":"done"}'

# Delete Task
curl -X DELETE http://localhost:3000/api/v1/tasks/2
TEXT 📖 للعرض فقط
// POST Created successfully → 201
Status: 201 Created
Location: /api/v1/tasks/3
{ "data": { "id": 3, "title": "Write documentation", "status": "pending", "priority": "high", "createdAt": "2025-07-03T10:30:00Z" } }

// PATCH Update successful → 200
{ "data": { "id": 1, "title": "Design database schema", "status": "done", "priority": "high", "createdAt": "2025-07-01T08:00:00Z" } }

// DELETE Success → 204
Status: 204 No Content
(empty body)

// 404 Error
{ "error": "Task not found", "details": [{ "field": "id", "message": "No task with id 999" }], "timestamp": "2025-07-03T10:30:00Z" }

// 400 Validation Error
{ "error": "Validation failed", "details": [{ "field": "title", "message": "Title is required" }], "timestamp": "2025-07-03T10:30:00Z" }


❓ أسئلة شائعة

س What is the difference between REST and GraphQL?
ج REST is resource-based; each URL corresponds to a resource, which is manipulated using HTTP methods. GraphQL is based on a query language; clients retrieve fields on demand from a single endpoint. REST is suitable for CRUD scenarios with well-defined resources, while GraphQL is suitable for complex relational queries.
س ما الفرق بين PUT و PATCH؟
ج تقوم PUT باستبدال كامل؛ حيث يجب عليك توفير جميع حقول المورد، وسيتم تعيين أي حقول مفقودة إلى قيمها الافتراضية. أما PATCH فتقوم بتحديث جزئي؛ فهي تُعدّل فقط الحقول التي توفرها، بينما تظل الحقول التي لم يتم توفيرها دون تغيير. وتتميز PUT بكونها متجانسة (idempotent)، في حين لا يُضمن أن تكون PATCH متجانسة.
س ما هي أفضل طريقة لتحديد إصدار واجهة برمجة التطبيقات (API)؟
ج يعد تحديد الإصدار عبر مسار عنوان URL (/api/v1/) الطريقة الأكثر بديهية؛ حيث يمكن اختبارها مباشرةً في المتصفح، وهي الخيار المفضل لمعظم واجهات برمجة التطبيقات العامة. أما تحديد الإصدار عبر رأس الطلب فهو أكثر توافقًا مع نمط REST، لكنه أكثر تعقيدًا عند تصحيح الأخطاء. وتعد معلمات الاستعلام هي أبسط الطرق، لكن من السهل إغفالها. يُنصح المبتدئين باستخدام تحديد الإصدار عبر مسار عنوان URL.
س هل يجب أن تُرجع واجهة برمجة التطبيقات (API) التي تعمل بنموذج REST صيغة JSON؟
ج ليس بالضرورة. لا يفرض نموذج REST أي قيود على الصيغة؛ فيمكنك استخدام XML وHTML وJSON وغيرها. ومع ذلك، تُعد صيغة JSON حالياً الأكثر استخداماً لأنها خفيفة الوزن وسهلة التحليل ومتوافقة أصلاً مع JavaScript. يمكنك تحديد الصيغة باستخدام رأس Content-Type.
س What is idempotence?
ج Idempotence means that executing the same request once produces the same result as executing it multiple times. GET is idempotent (multiple reads yield the same result), PUT is idempotent (multiple replacements yield the same result), DELETE is idempotent (deleting a resource that has already been deleted still returns a success), and POST is not idempotent (it creates a new resource each time).
س ماذا أفعل إذا كانت التسلسل الهرمي للموارد عميقًا جدًّا؟
ج إذا تجاوز التسلسل الهرمي مستويين من التداخل، ففكر في ترقية الموارد الفرعية إلى موارد من المستوى الأعلى وربطها باستخدام معلمات الاستعلام. على سبيل المثال، إذا كان /users/42/tasks/1/comments/5 عميقًا جدًّا، فيمكنك تغييره إلى /comments/5 أو /tasks/1/comments/5.
س كيف تتعامل واجهة برمجة التطبيقات (API) التي تعمل بنموذج REST مع العمليات الجماعية؟
ج لا توجد طريقة موحدة في نموذج REST. وتشمل الممارسات الشائعة ما يلي: استخدام طلب POST /tasks/batch مع مصفوفة لإنشاء السجلات؛ واستخدام طلب PATCH /tasks مع مصفوفة لتحديث السجلات بشكل جماعي؛ واستخدام طلب DELETE /tasks?ids=1,2,3 لحذف السجلات بشكل جماعي. يجب توثيق نقاط النهاية المخصصة بوضوح.

📖 ملخص


📝 تمارين

  1. أكمل جميع أمثلة الأكواد الواردة في هذا الدرس وتأكد من أن كل منها يعمل بشكل صحيح.
  2. قم بتعديل المثال الشامل وأضف الإضافات الخاصة بك
  3. راجع الوثائق الرسمية، وحدد واجهة برمجة تطبيقات (API) واحدة أو اثنتين لم يتم تناولهما في هذا الدرس، واكتب كود اختبار لهما.
  4. التأمل: كيف ستطبق ما تعلمته في هذا الدرس على مشروع في الواقع العملي؟
  5. حاول أن تجمع بين ما تعلمته في هذا الدرس والمواد التي درستها في الدروس السابقة لإنشاء مشروع صغير.
Web-Tutorial.com

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

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

100%