Node.js: تصميم REST API
آخر تحديث: 2026-08-26
يعمل فريق أليس على تطوير كل من الواجهة الأمامية والواجهة الخلفية لنظام إدارة المهام. ويشكو مطورو الواجهة الأمامية من أنهم لا يعرفون أي واجهات برمجة التطبيقات (APIs) متاحة، أو أي طرق HTTP يجب استخدامها، أو ما هي التنسيقات التي ستكون عليها الردود. ويشعر مطورو الواجهة الخلفية بنفس القدر من الإحباط — ففي عملية «تحديث المهمة» نفسها، يستخدم البعض طريقة POST، والبعض الآخر طريقة PUT، والبعض الآخر طريقة PATCH، مما يؤدي إلى تنوع كبير في تنسيقات الردود. وقد تحول التعاون إلى فوضى عارمة.
قررت أليس اعتماد مواصفات REST. وبعد أن قام الفريق بتوحيد تسمية الموارد، وتعيين الطرق، ورموز الحالة، وتنسيقات الاستجابات، أصبحت واجهة برمجة التطبيقات (API) واضحة ويمكن التنبؤ بها؛ ولم يعد فريق الواجهة الأمامية مضطرًا إلى مراجعة وثائق واجهة برمجة التطبيقات (API) مرارًا وتكرارًا، وتضاعفت كفاءة التعاون.
1. ما ستتعلمه
- المبادئ الأساسية الأربعة لهندسة REST
- التعيين الصحيح لعمليات CRUD إلى طرق HTTP
- ما يجب فعله وما يجب تجنبه في تصميم عناوين URL وفقًا لمعايير RESTful
- استراتيجيات اختيار رموز حالة HTTP
- مواصفات JSON للطلبات والاستجابات
- ثلاث استراتيجيات لإدارة إصدارات واجهة برمجة التطبيقات (API)
- نموذج نضج REST ومبدأ HATEOAS
2. مبادئ بنية REST
(1) ما هو REST؟
REST (نقل الحالة التمثيلية) هو أسلوب هندسي برمجي اقترحه روي فيلدينغ في عام 2000. وهو يحدد مجموعة من القيود لتصميم واجهات تطبيقات الويب. ولا يُعد REST بروتوكولاً أو معياراً، بل هو فلسفة تصميمية.
(2) أربعة مبادئ أساسية
| المبدأ | المعنى | مثال |
|---|---|---|
| المورد | كل شيء يُعد موردًا، ويُحدد بواسطة عنوان URL | /tasks، /users/42 |
| طبقة التمثيل | التنسيق الذي يتم به تمثيل المورد، مثل JSON | {"id": 1, "タイトル": "Learn REST"} |
| بدون حالة | يحتوي كل طلب على جميع المعلومات الضرورية | تتضمن الطلبات رمزًا مميزًا ولا تعتمد على الجلسات |
| واجهة موحدة | التعامل مع الموارد باستخدام طرق HTTP القياسية | GET للقراءة، وPOST للإنشاء، وDELETE للحذف |
▶ مثال: بدون حالة مقابل مع حالة
// 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
// 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
// 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 لنظام إدارة المهام
# 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
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) الأخطاء الشائعة: الاستخدام غير الصحيح لرموز الحالة
▶ مثال: الاستخدام الصحيح لرموز الحالة
// 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"} |
▶ مثال: رد قائمة موحد
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)
}
});
});
// Response Example
{
"data": [
{
"id": "1",
"タイトル": "Learn REST",
"status": "pending",
"createdAt": "2025-07-03T10:30:00Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 1,
"totalPages": 1
}
}
▶ مثال: الاستجابة القياسية للخطأ
// 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);
});
// 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) أفضل الممارسات في إدارة الإصدارات
- بدءًا من الإصدار 1، لا تحذف رقم الإصدار
- لا تقم بالترقية إلى إصدار رئيسي إلا في حالة وجود تغييرات جذرية
- سيتم دعم الإصدارات الأقدم لمدة 6 أشهر على الأقل
- تحديد الإصدار الحالي في رأس الاستجابة
▶ مثال: تطبيق نظام إصدارات مسارات عناوين URL
// 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:
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
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' }
}
});
});
// 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) تعيين الطرق والطلبات/الاستجابات
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'));
(3) مرجع سريع للطلبات والردود
# 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
// 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" }
❓ أسئلة شائعة
/api/v1/) الطريقة الأكثر بديهية؛ حيث يمكن اختبارها مباشرةً في المتصفح، وهي الخيار المفضل لمعظم واجهات برمجة التطبيقات العامة. أما تحديد الإصدار عبر رأس الطلب فهو أكثر توافقًا مع نمط REST، لكنه أكثر تعقيدًا عند تصحيح الأخطاء. وتعد معلمات الاستعلام هي أبسط الطرق، لكن من السهل إغفالها. يُنصح المبتدئين باستخدام تحديد الإصدار عبر مسار عنوان URL.Content-Type./users/42/tasks/1/comments/5 عميقًا جدًّا، فيمكنك تغييره إلى /comments/5 أو /tasks/1/comments/5./tasks/batch مع مصفوفة لإنشاء السجلات؛ واستخدام طلب PATCH /tasks مع مصفوفة لتحديث السجلات بشكل جماعي؛ واستخدام طلب DELETE /tasks?ids=1,2,3 لحذف السجلات بشكل جماعي. يجب توثيق نقاط النهاية المخصصة بوضوح.📖 ملخص
- المفاهيم الأساسية وكيفية تطبيقها
- 1 المفاهيم الأساسية واستخدام مبادئ بنية REST
- 2 المفاهيم الأساسية واستخدامات CRUD وتعيين طرق HTTP
- 3 مفاهيم أساسية واستخدامات إرشادات تصميم عناوين URL
- 4 مفاهيم أساسية واستخدامات رموز حالة HTTP
- 5 مفاهيم أساسية واستخدامات تنسيقات الطلبات والاستجابات
- 6 مفاهيم أساسية واستخدامات استراتيجيات تحديد إصدارات واجهة برمجة التطبيقات (API)
- 7 مفاهيم أساسية واستخدامات نموذج نضج REST
📝 تمارين
- أكمل جميع أمثلة الأكواد الواردة في هذا الدرس وتأكد من أن كل منها يعمل بشكل صحيح.
- قم بتعديل المثال الشامل وأضف الإضافات الخاصة بك
- راجع الوثائق الرسمية، وحدد واجهة برمجة تطبيقات (API) واحدة أو اثنتين لم يتم تناولهما في هذا الدرس، واكتب كود اختبار لهما.
- التأمل: كيف ستطبق ما تعلمته في هذا الدرس على مشروع في الواقع العملي؟
- حاول أن تجمع بين ما تعلمته في هذا الدرس والمواد التي درستها في الدروس السابقة لإنشاء مشروع صغير.