تطوير واجهات REST API
بناء أول واجهة برمجة REST الخاصة بك
حان وقت البناء!
لقد تعلمت النظرية وراء واجهات برمجة REST - أساليب HTTP، رموز الحالة، تصميم URL، وتنسيقات البيانات. حان الوقت الآن لوضع هذه المعرفة موضع التطبيق من خلال بناء واجهة برمجة REST حقيقية من الصفر.
ملاحظة: يستخدم هذا الدرس Node.js مع إطار Express للأمثلة، ولكن المفاهيم تنطبق على أي لغة (PHP، Python، Java، إلخ). ركز على فهم مبادئ REST بدلاً من حفظ بناء الجملة.
ما الذي سنبنيه
نحن نبني واجهة برمجة بسيطة لإدارة المهام مع هذه الميزات:
- إنشاء وقراءة وتحديث وحذف المهام (عمليات CRUD)
- سرد جميع المهام مع التصفية والترقيم
- وضع علامة على المهام كمكتملة/غير مكتملة
- رموز حالة HTTP المناسبة ومعالجة الأخطاء
- تنسيق طلب/استجابة JSON
إعداد المشروع
المتطلبات الأساسية:
- تثبيت Node.js (الإصدار 14 أو أعلى)
- معرفة أساسية بـ JavaScript
- محرر كود (يُوصى بـ VS Code)
- الوصول إلى الطرفية/موجه الأوامر
الخطوة 1: تهيئة المشروع
# إنشاء دليل المشروع
mkdir task-api
cd task-api
# تهيئة مشروع npm
npm init -y
# تثبيت التبعيات
npm install express
npm install --save-dev nodemon
# إنشاء هيكل المشروع
mkdir src
touch src/server.js
touch src/routes.js
الخطوة 2: تكوين Package.json
قم بتحديث package.json الخاص بك لتضمين نصوص بدء التشغيل:
{
"name": "task-api",
"version": "1.0.0",
"description": "واجهة برمجة REST بسيطة لإدارة المهام",
"main": "src/server.js",
"scripts": {
"start": "node src/server.js",
"dev": "nodemon src/server.js"
},
"dependencies": {
"express": "^4.18.2"
},
"devDependencies": {
"nodemon": "^3.0.1"
}
}
بناء الخادم
الخطوة 3: إنشاء خادم أساسي (src/server.js)
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
// البرمجية الوسيطة لتحليل أجسام طلبات JSON
app.use(express.json());
// مسار ترحيب بسيط
app.get('/', (req, res) => {
res.json({
message: 'مرحباً بك في واجهة برمجة إدارة المهام',
version: '1.0.0',
endpoints: {
tasks: '/api/v1/tasks'
}
});
});
// بدء الخادم
app.listen(PORT, () => {
console.log(`الخادم يعمل على http://localhost:${PORT}`);
});
module.exports = app;
الخطوة 4: اختبر خادمك
# بدء الخادم
npm run dev
# يجب أن يبدأ الخادم على http://localhost:3000
# قم بزيارة http://localhost:3000 في متصفحك
# يجب أن ترى استجابة JSON الترحيبية
نصيحة: استخدام nodemon في التطوير يعيد تشغيل الخادم تلقائياً عند حفظ التغييرات. هذا يسرع التطوير بشكل كبير.
إنشاء نموذج المهمة
الخطوة 5: مخزن بيانات في الذاكرة
للبساطة، سنستخدم مصفوفة في الذاكرة. في الإنتاج، ستستخدم قاعدة بيانات مثل MongoDB أو PostgreSQL أو MySQL.
// أضف إلى src/server.js بعد تهيئة التطبيق
// تخزين المهام في الذاكرة (استبدل بقاعدة بيانات في الإنتاج)
let tasks = [
{
id: 1,
title: 'تعلم واجهات برمجة REST',
description: 'إكمال دورة واجهات برمجة REST',
completed: false,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
},
{
id: 2,
title: 'بناء واجهة برمجة نموذجية',
description: 'إنشاء واجهة برمجة لإدارة المهام',
completed: false,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
}
];
// عداد لتوليد معرفات جديدة
let nextId = 3;
تنفيذ نقاط نهاية CRUD
GET /api/v1/tasks - سرد جميع المهام
// الحصول على جميع المهام (مع تصفية اختيارية)
app.get('/api/v1/tasks', (req, res) => {
const { completed, search } = req.query;
let filteredTasks = tasks;
// تصفية حسب حالة الإكمال
if (completed !== undefined) {
const isCompleted = completed === 'true';
filteredTasks = filteredTasks.filter(task => task.completed === isCompleted);
}
// البحث في العنوان والوصف
if (search) {
const searchLower = search.toLowerCase();
filteredTasks = filteredTasks.filter(task =>
task.title.toLowerCase().includes(searchLower) ||
task.description.toLowerCase().includes(searchLower)
);
}
res.json({
data: filteredTasks,
meta: {
total: filteredTasks.length,
filtered: completed !== undefined || search !== undefined
}
});
});
GET /api/v1/tasks/:id - الحصول على مهمة واحدة
// الحصول على مهمة واحدة حسب المعرف
app.get('/api/v1/tasks/:id', (req, res) => {
const taskId = parseInt(req.params.id);
const task = tasks.find(t => t.id === taskId);
if (!task) {
return res.status(404).json({
error: {
code: 'TASK_NOT_FOUND',
message: `المهمة بالمعرف ${taskId} غير موجودة`
}
});
}
res.json({
data: task
});
});
POST /api/v1/tasks - إنشاء مهمة جديدة
// إنشاء مهمة جديدة
app.post('/api/v1/tasks', (req, res) => {
const { title, description } = req.body;
// التحقق
const errors = [];
if (!title || title.trim() === '') {
errors.push({
field: 'title',
message: 'العنوان مطلوب'
});
}
if (title && title.length < 3) {
errors.push({
field: 'title',
message: 'يجب أن يكون العنوان 3 أحرف على الأقل'
});
}
if (errors.length > 0) {
return res.status(422).json({
error: {
code: 'VALIDATION_ERROR',
message: 'فشل التحقق',
details: errors
}
});
}
// إنشاء مهمة جديدة
const newTask = {
id: nextId++,
title: title.trim(),
description: description ? description.trim() : '',
completed: false,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
tasks.push(newTask);
res.status(201).json({
data: newTask
});
});
مهم: لاحظ أننا نعيد رمز الحالة 201 (تم الإنشاء) للإنشاء الناجح، وليس 200. نحن أيضاً نتحقق من الإدخال قبل إنشاء المورد.
PUT /api/v1/tasks/:id - تحديث المهمة بالكامل
// تحديث المهمة بالكامل (PUT)
app.put('/api/v1/tasks/:id', (req, res) => {
const taskId = parseInt(req.params.id);
const taskIndex = tasks.findIndex(t => t.id === taskId);
if (taskIndex === -1) {
return res.status(404).json({
error: {
code: 'TASK_NOT_FOUND',
message: `المهمة بالمعرف ${taskId} غير موجودة`
}
});
}
const { title, description, completed } = req.body;
// التحقق
if (!title || title.trim() === '') {
return res.status(422).json({
error: {
code: 'VALIDATION_ERROR',
message: 'العنوان مطلوب'
}
});
}
// تحديث المهمة (استبدال المورد بالكامل)
tasks[taskIndex] = {
id: taskId,
title: title.trim(),
description: description ? description.trim() : '',
completed: completed === true,
createdAt: tasks[taskIndex].createdAt, // احتفظ بتاريخ الإنشاء الأصلي
updatedAt: new Date().toISOString()
};
res.json({
data: tasks[taskIndex]
});
});
PATCH /api/v1/tasks/:id - تحديث جزئي
// تحديث المهمة جزئياً (PATCH)
app.patch('/api/v1/tasks/:id', (req, res) => {
const taskId = parseInt(req.params.id);
const task = tasks.find(t => t.id === taskId);
if (!task) {
return res.status(404).json({
error: {
code: 'TASK_NOT_FOUND',
message: `المهمة بالمعرف ${taskId} غير موجودة`
}
});
}
// تحديث الحقول المقدمة فقط
if (req.body.title !== undefined) {
if (req.body.title.trim() === '') {
return res.status(422).json({
error: {
code: 'VALIDATION_ERROR',
message: 'لا يمكن أن يكون العنوان فارغاً'
}
});
}
task.title = req.body.title.trim();
}
if (req.body.description !== undefined) {
task.description = req.body.description.trim();
}
if (req.body.completed !== undefined) {
task.completed = req.body.completed === true;
}
task.updatedAt = new Date().toISOString();
res.json({
data: task
});
});
DELETE /api/v1/tasks/:id - حذف المهمة
// حذف المهمة
app.delete('/api/v1/tasks/:id', (req, res) => {
const taskId = parseInt(req.params.id);
const taskIndex = tasks.findIndex(t => t.id === taskId);
if (taskIndex === -1) {
return res.status(404).json({
error: {
code: 'TASK_NOT_FOUND',
message: `المهمة بالمعرف ${taskId} غير موجودة`
}
});
}
// إزالة المهمة من المصفوفة
tasks.splice(taskIndex, 1);
// إرجاع 204 No Content (لا يوجد جسم استجابة)
res.status(204).send();
});
نقطة نهاية إجراء خاص
POST /api/v1/tasks/:id/toggle - تبديل الإكمال
// تبديل إكمال المهمة (إجراء خاص)
app.post('/api/v1/tasks/:id/toggle', (req, res) => {
const taskId = parseInt(req.params.id);
const task = tasks.find(t => t.id === taskId);
if (!task) {
return res.status(404).json({
error: {
code: 'TASK_NOT_FOUND',
message: `المهمة بالمعرف ${taskId} غير موجودة`
}
});
}
// تبديل حالة الإكمال
task.completed = !task.completed;
task.updatedAt = new Date().toISOString();
res.json({
data: task,
meta: {
action: 'toggled',
newStatus: task.completed ? 'مكتملة' : 'غير مكتملة'
}
});
});
البرمجية الوسيطة لمعالجة الأخطاء
أضف معالجة الأخطاء العامة في نهاية ملف server.js الخاص بك:
// التعامل مع 404 - المسار غير موجود
app.use((req, res) => {
res.status(404).json({
error: {
code: 'ENDPOINT_NOT_FOUND',
message: `لا يمكن ${req.method} ${req.path}`,
availableEndpoints: [
'GET /api/v1/tasks',
'GET /api/v1/tasks/:id',
'POST /api/v1/tasks',
'PUT /api/v1/tasks/:id',
'PATCH /api/v1/tasks/:id',
'DELETE /api/v1/tasks/:id',
'POST /api/v1/tasks/:id/toggle'
]
}
});
});
// التعامل مع 500 - أخطاء الخادم
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({
error: {
code: 'INTERNAL_SERVER_ERROR',
message: 'حدث خطأ ما على الخادم',
...(process.env.NODE_ENV === 'development' && { details: err.message })
}
});
});
تحذير: لا تكشف أبداً عن رسائل خطأ مفصلة أو تتبعات المكدس في الإنتاج. يمكن أن تكشف عن معلومات حساسة حول بنية نظامك.
اختبار واجهة برمجة التطبيقات الخاصة بك
استخدام cURL (سطر الأوامر):
# الحصول على جميع المهام
curl http://localhost:3000/api/v1/tasks
# الحصول على مهمة واحدة
curl http://localhost:3000/api/v1/tasks/1
# إنشاء مهمة جديدة
curl -X POST http://localhost:3000/api/v1/tasks \
-H "Content-Type: application/json" \
-d '{"title":"مهمة جديدة","description":"مهمة اختبار"}'
# تحديث المهمة (جزئي)
curl -X PATCH http://localhost:3000/api/v1/tasks/1 \
-H "Content-Type: application/json" \
-d '{"completed":true}'
# حذف المهمة
curl -X DELETE http://localhost:3000/api/v1/tasks/1
# تصفية المهام المكتملة
curl http://localhost:3000/api/v1/tasks?completed=true
# البحث عن المهام
curl http://localhost:3000/api/v1/tasks?search=واجهة
استخدام Postman:
- قم بتنزيل وتثبيت Postman (أداة مجانية)
- إنشاء طلب جديد
- تعيين الأسلوب (GET، POST، PUT، PATCH، DELETE)
- أدخل URL: http://localhost:3000/api/v1/tasks
- لـ POST/PUT/PATCH: حدد "Body" → "raw" → "JSON"
- أدخل بيانات JSON وانقر على Send
استخدام امتداد VS Code REST Client:
أنشئ ملف test-api.http:
### الحصول على جميع المهام
GET http://localhost:3000/api/v1/tasks
### الحصول على مهمة واحدة
GET http://localhost:3000/api/v1/tasks/1
### إنشاء مهمة جديدة
POST http://localhost:3000/api/v1/tasks
Content-Type: application/json
{
"title": "تعلم واجهات برمجة REST",
"description": "إكمال البرنامج التعليمي وبناء واجهة برمجية"
}
### تحديث المهمة
PATCH http://localhost:3000/api/v1/tasks/1
Content-Type: application/json
{
"completed": true
}
### حذف المهمة
DELETE http://localhost:3000/api/v1/tasks/1
توثيق واجهة البرمجة
واجهات البرمجة الجيدة موثقة بشكل جيد. إليك ملف README بسيط لواجهتك البرمجية:
# واجهة برمجة إدارة المهام
عنوان URL الأساسي: `http://localhost:3000/api/v1`
## نقاط النهاية
| الأسلوب | نقطة النهاية | الوصف |
|--------|----------|-------------|
| GET | /tasks | سرد جميع المهام |
| GET | /tasks/:id | الحصول على مهمة واحدة |
| POST | /tasks | إنشاء مهمة جديدة |
| PUT | /tasks/:id | تحديث المهمة بالكامل |
| PATCH | /tasks/:id | تحديث المهمة جزئياً |
| DELETE | /tasks/:id | حذف المهمة |
| POST | /tasks/:id/toggle | تبديل الإكمال |
## معاملات الاستعلام
- `completed`: تصفية حسب الحالة (true/false)
- `search`: البحث في العنوان والوصف
## أمثلة الاستجابات
**نجاح (200 OK):**
```json
{
"data": {
"id": 1,
"title": "عنوان المهمة",
"completed": false
}
}
```
**خطأ (404 Not Found):**
```json
{
"error": {
"code": "TASK_NOT_FOUND",
"message": "المهمة غير موجودة"
}
}
```
نصيحة: فكر في استخدام أدوات مثل Swagger/OpenAPI لإنشاء توثيق واجهة برمجية تفاعلي تلقائياً من كودك.
أفضل الممارسات المطبقة
واجهتنا البرمجية توضح مبادئ REST هذه:
- ✅ عناوين URL قائمة على الموارد (/tasks، وليس /getTasks)
- ✅ أساليب HTTP المناسبة (GET، POST، PUT، PATCH، DELETE)
- ✅ رموز الحالة الصحيحة (200، 201، 204، 404، 422، 500)
- ✅ تنسيق JSON لجميع الطلبات/الاستجابات
- ✅ هيكل استجابة متسق (data + meta/error)
- ✅ التحقق من الإدخال مع رسائل خطأ مفيدة
- ✅ قدرات التصفية والبحث
- ✅ إصدار واجهة البرمجة (/api/v1/)
- ✅ طوابع زمنية ISO 8601
- ✅ نقطة نهاية إجراء RESTful (toggle)
الخطوات التالية: تحسين واجهتك البرمجية
1. إضافة الترقيم
app.get('/api/v1/tasks', (req, res) => {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const startIndex = (page - 1) * limit;
const endIndex = page * limit;
const paginatedTasks = tasks.slice(startIndex, endIndex);
res.json({
data: paginatedTasks,
meta: {
current_page: page,
per_page: limit,
total: tasks.length,
total_pages: Math.ceil(tasks.length / limit)
}
});
});
2. إضافة الفرز
const sortBy = req.query.sort || 'createdAt';
const order = req.query.order === 'asc' ? 1 : -1;
tasks.sort((a, b) => {
if (a[sortBy] < b[sortBy]) return -1 * order;
if (a[sortBy] > b[sortBy]) return 1 * order;
return 0;
});
3. إضافة المصادقة
// البرمجية الوسيطة للتحقق من المصادقة
const authenticate = (req, res, next) => {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({
error: {
code: 'UNAUTHORIZED',
message: 'المصادقة مطلوبة'
}
});
}
// تحقق من الرمز هنا
next();
};
// تطبيق على المسارات المحمية
app.get('/api/v1/tasks', authenticate, (req, res) => {
// معالج المسار
});
4. الاتصال بقاعدة بيانات
استبدل المصفوفة في الذاكرة بـ MongoDB أو PostgreSQL أو MySQL للتخزين المستمر.
تمرين: توسيع واجهة البرمجة
أضف هذه الميزات إلى واجهة برمجة المهام الخاصة بك:
- أضف حقل "أولوية" (منخفض، متوسط، عالي) إلى المهام
- أنشئ نقطة نهاية للحصول على إحصائيات المهام (الإجمالي، المكتمل، المعلق)
- أضف القدرة على تعيين المهام للمستخدمين (افترض معرفات المستخدم)
- نفذ تواريخ الاستحقاق مع التحقق (لا يمكن أن تكون في الماضي)
- أضف نقطة نهاية للحصول على المهام المتأخرة
النقاط الرئيسية
- واجهات برمجة REST مبنية على أساليب HTTP ورموز الحالة القياسية
- تحقق دائماً من الإدخال قبل المعالجة
- أرجع رموز الحالة المناسبة (201 للإنشاء، 204 للحذف)
- هيكل الاستجابات بشكل متسق (أغلفة data/error)
- وفر رسائل خطأ مفيدة مع رموز
- قم بإصدار واجهتك البرمجية من البداية (/api/v1/)
- اختبر بدقة باستخدام أدوات مثل Postman أو cURL
- وثق نقاط نهاية واجهتك البرمجية والتنسيقات المتوقعة
تهانينا! لقد قمت ببناء أول واجهة برمجة REST من الصفر! أنت الآن تفهم كيفية تصميم نقاط النهاية ومعالجة الطلبات والتحقق من البيانات وإرجاع الاستجابات المناسبة. الخطوة التالية هي ممارسة بناء واجهات برمجية أكثر تعقيداً واستكشاف موضوعات متقدمة مثل المصادقة وتحديد المعدل وأمان واجهة البرمجة.