Node.js: مشروع RESTful API

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

1. خلفية المشروع

كُلفت أليس وبوب بمهمة تتمثل في تطوير واجهة برمجة تطبيقات (API) لإدارة الكتب لصالح مكتبة إلكترونية. كانت أليس مسؤولة عن تصميم بنية التوجيه والبرمجيات الوسيطة، بينما كان بوب مسؤولاً عن كتابة المنطق التجاري. اتفق الاثنان على تنسيق موحد للاستجابات وقواعد معيارية لمعالجة الأخطاء، وفي غضون ساعتين، أكملوا المشروع بأكمله — بدءًا من إعداد المشروع وصولاً إلى تنفيذ وظائف CRUD الكاملة.

(1) ستتعلم



2. تصميم بنية دليل المشروع

يُعد التسلسل الهرمي المنظم جيدًا للمجلدات أساس قابلية صيانة المشروع. ووفقًا لأفضل الممارسات المتبعة في المجتمع، تنظم أليس المشروع إلى طبقات بناءً على المسؤوليات:

TEXT 📖 للعرض فقط
book-api/
├── app.js
├── routes/
│   └── books.js
├── controllers/
│   └── bookController.js
├── middleware/
│   ├── validate.js
│   └── errorHandler.js
└── models/
    └── bookModel.js
الدليل/الملف المسؤوليات الوصف
app.js ملف الإدخال إنشاء مثيل لـ Express، وربط المسارات والبرامج الوسيطة
routes/ تعريف المسار يحدد طرق HTTP والمسارات التي تشير إلى وحدة التحكم المقابلة
controllers/ منطق الأعمال معالجة الطلب، واستدعاء النموذج، وإرجاع استجابة
middleware/ البرمجيات الوسيطة القضايا الشاملة مثل التحقق من صحة الطلبات ومعالجة الأخطاء
models/ نماذج البيانات تخزين البيانات وتغليف عمليات البيانات


3. تصميم نقاط نهاية واجهة برمجة التطبيقات (API)

بناءً على متطلبات العمل، قام بوب بتجميع قائمة بجميع نقاط نهاية واجهة برمجة التطبيقات (API):

طريقة HTTP المسار الوظيفة رمز حالة النجاح رمز حالة الفشل
الحصول على /api/books الحصول على جميع الكتب 200
GET /api/books/:id استرداد كتاب واحد 200 404
منشور /api/books كتب جديدة 201 400
PUT /api/books/:id تحديث الكتاب 200 404 / 400
DELETE /api/books/:id Delete Book 200 404


4. نموذج الرد الموحد

تصر أليس على اتباع نمط موحد للردود حتى لا يضطر فريق الواجهة الأمامية إلى تخمين أسماء الحقول:

▶ مثال: رد ناجح

JAVASCRIPT
{
  "success": true,
  "data": { "id": 1, "title": "Node.js Guide", "author": "Alice" }
}
▶ جرّب الكود

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

JAVASCRIPT
{
  "success": false,
  "error": { "code": 404, "message": "Book not found" }
}
▶ جرّب الكود
الحقل النوع الوصف
success منطقية ما إذا كان الطلب قد نجح أم لا
data أي البيانات التي يتم إرجاعها في حالة النجاح
error.code الرقم رمز حالة الخطأ
error.message سلسلة وصف الخطأ


5. طبقة نموذج البيانات

يستخدم بوب المصفوفات الموجودة في الدليل models لمحاكاة قاعدة بيانات وتغليف جميع عمليات البيانات:

▶ مثال: bookModel.js

JAVASCRIPT
const books = [
  { id: 1, title: "Node.js Guide", author: "Alice", year: 2024 }
];

let nextId = 2;

function findAll() {
  return books;
}

function findById(id) {
  return books.find(b => b.id === id);
}

function create(data) {
  const book = { id: nextId++, ...data };
  books.push(book);
  return book;
}

function update(id, data) {
  const index = books.findIndex(b => b.id === id);
  if (index === -1) return null;
  books[index] = { ...books[index], ...data };
  return books[index];
}

function remove(id) {
  const index = books.findIndex(b => b.id === id);
  if (index === -1) return false;
  books.splice(index, 1);
  return true;
}

module.exports = { findAll, findById, create, update, remove };
▶ جرّب الكود

6. البرمجيات الوسيطة للتحقق من صحة الطلبات

تقوم «أليس» بتنفيذ عملية التحقق من صحة البيانات في طبقة البرمجيات الوسيطة لضمان عدم وصول الطلبات غير الصحيحة إلى وحدة التحكم:

▶ مثال: validate.js

JAVASCRIPT
function validateBook(req, res, next) {
  const { title, author, year } = req.body;
  const errors = [];
  if (!title || typeof title !== "string") {
    errors.push("title is required and must be a string");
  }
  if (!author || typeof author !== "string") {
    errors.push("author is required and must be a string");
  }
  if (year !== undefined && (typeof year !== "number" || year < 0)) {
    errors.push("year must be a non-negative number");
  }
  if (errors.length > 0) {
    return res.status(400).json({
      success: false,
      error: { code: 400, message: errors.join("; ") }
    });
  }
  next();
}

module.exports = { validateBook };
▶ جرّب الكود

7. البرمجيات الوسيطة لمعالجة الأخطاء

▶ مثال: errorHandler.js

JAVASCRIPT
function errorHandler(err, req, res, next) {
  console.error(err.stack);
  const status = err.status || 500;
  res.status(status).json({
    success: false,
    error: { code: status, message: err.message || "Internal Server Error" }
  });
}

function createError(status, message) {
  const err = new Error(message);
  err.status = status;
  return err;
}

module.exports = { errorHandler, createError };
▶ جرّب الكود

8. طبقة وحدة التحكم

يتولى «بوب» معالجة منطق العمل في وحدة التحكم، ويستدعي النموذج، ويعرض استجابة بتنسيق موحد:

▶ مثال: bookController.js

JAVASCRIPT
const Book = require("../models/bookModel");
const { createError } = require("../middleware/errorHandler");

function getAllBooks(req, res) {
  res.json({ success: true, data: Book.findAll() });
}

function getBookById(req, res, next) {
  const book = Book.findById(Number(req.params.id));
  if (!book) return next(createError(404, "Book not found"));
  res.json({ success: true, data: book });
}

function createBook(req, res) {
  const book = Book.create(req.body);
  res.status(201).json({ success: true, data: book });
}

function updateBook(req, res, next) {
  const book = Book.update(Number(req.params.id), req.body);
  if (!book) return next(createError(404, "Book not found"));
  res.json({ success: true, data: book });
}

function deleteBook(req, res, next) {
  const removed = Book.remove(Number(req.params.id));
  if (!removed) return next(createError(404, "Book not found"));
  res.json({ success: true, data: { message: "Book deleted" } });
}

module.exports = { getAllBooks, getBookById, createBook, updateBook, deleteBook };
▶ جرّب الكود

9. تعريفات المسارات

تفصل «أليس» عملية التوجيه عن وحدة التحكم؛ حيث تقتصر مسؤولية عملية التوجيه على التعيين فقط:

▶ مثال: routes/books.js

JAVASCRIPT
const express = require("express");
const router = express.Router();
const controller = require("../controllers/bookController");
const { validateBook } = require("../middleware/validate");

router.get("/", controller.getAllBooks);
router.get("/:id", controller.getBookById);
router.post("/", validateBook, controller.createBook);
router.put("/:id", validateBook, controller.updateBook);
router.delete("/:id", controller.deleteBook);

module.exports = router;
▶ جرّب الكود

10. ملف app.js

▶ مثال: app.js

JAVASCRIPT
const express = require("express");
const booksRoute = require("./routes/books");
const { errorHandler } = require("./middleware/errorHandler");

const app = express();
app.use(express.json());

app.use("/api/books", booksRoute);
app.use(errorHandler);

const PORT = 3000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
▶ جرّب الكود

11. نظرة عامة على بنية المشروع

رسمت أليس مخططًا هندسيًّا يوضح بوضوح مسار الطلبات:

100%
graph TD
    A[app.js Entrance] --> B[routes/books.js]
    B --> C{Needs verification?}
    C -->|POST/PUT| D[middleware/validate.js]
    C -->|GET/DELETE| E[controllers/bookController.js]
    D --> E
    E --> F[models/bookModel.js]
    F --> E
    E --> G[Standardized Response Format]
    G --> H[Client]
    E -->|Exception| I[middleware/errorHandler.js]
    I --> H


12. اختبار واجهة برمجة التطبيقات (API)

يستخدم بوب curl للتحقق من كل نقطة نهاية على حدة:

أمر curl الوصف
curl localhost:3000/api/books الحصول على جميع الكتب
curl localhost:3000/api/books/1 الحصول على كتاب معين
curl -X POST -H "Content-Type: application/json" -d '{"title":"New Book","author":"Bob","year":2025}' localhost:3000/api/books كتب جديدة
curl -X PUT -H "Content-Type: application/json" -d '{"year":2026}' localhost:3000/api/books/1 تحديث الكتب
curl -X DELETE localhost:3000/api/books/1 حذف الكتاب

▶ مثال: اختبار عمليات الإدراج والاستعلام

BASH
# Add a book
curl -X POST -H "Content-Type: application/json" \
  -d '{"title":"Express in Action","author":"Evan","year":2024}' \
  localhost:3000/api/books
TEXT 📖 للعرض فقط
{"success":true,"data":{"id":2,"タイトル":"Express in Action","author":"Evan","year":2024}}
BASH
# Search All Books
curl localhost:3000/api/books
TEXT 📖 للعرض فقط
{"success":true,"data":[{"id":1,"title":"Node.js Guide","author":"Alice","year":2024},{"id":2,"title":"Express in Action","author":"Evan","year":2024}]}


13. مثال شامل: واجهة برمجة تطبيقات (API) كاملة لإدارة الكتب

بالجمع بين جميع الوحدات السابقة، إليكم مسار الكود الأساسي للمشروع الكامل:

JAVASCRIPT
// models/bookModel.js
const books = [{ id: 1, title: "Node.js Guide", author: "Alice", year: 2024 }];
let nextId = 2;
function findAll() { return books; }
function findById(id) { return books.find(b => b.id === id); }
function create(data) {
  const book = { id: nextId++, ...data };
  books.push(book);
  return book;
}
function update(id, data) {
  const index = books.findIndex(b => b.id === id);
  if (index === -1) return null;
  books[index] = { ...books[index], ...data };
  return books[index];
}
function remove(id) {
  const index = books.findIndex(b => b.id === id);
  if (index === -1) return false;
  books.splice(index, 1);
  return true;
}
module.exports = { findAll, findById, create, update, remove };
JAVASCRIPT
// middleware/validate.js
function validateBook(req, res, next) {
  const { title, author } = req.body;
  const errors = [];
  if (!title || typeof title !== "string") errors.push("title is required");
  if (!author || typeof author !== "string") errors.push("author is required");
  if (errors.length > 0) {
    return res.status(400).json({
      success: false,
      error: { code: 400, message: errors.join("; ") }
    });
  }
  next();
}
module.exports = { validateBook };
JAVASCRIPT
// middleware/errorHandler.js
function errorHandler(err, req, res, next) {
  const status = err.status || 500;
  res.status(status).json({
    success: false,
    error: { code: status, message: err.message || "Internal Server Error" }
  });
}
function createError(status, message) {
  const err = new Error(message);
  err.status = status;
  return err;
}
module.exports = { errorHandler, createError };
JAVASCRIPT
// controllers/bookController.js
const Book = require("../models/bookModel");
const { createError } = require("../middleware/errorHandler");
function getAllBooks(req, res) {
  res.json({ success: true, data: Book.findAll() });
}
function getBookById(req, res, next) {
  const book = Book.findById(Number(req.params.id));
  if (!book) return next(createError(404, "Book not found"));
  res.json({ success: true, data: book });
}
function createBook(req, res) {
  const book = Book.create(req.body);
  res.status(201).json({ success: true, data: book });
}
function updateBook(req, res, next) {
  const book = Book.update(Number(req.params.id), req.body);
  if (!book) return next(createError(404, "Book not found"));
  res.json({ success: true, data: book });
}
function deleteBook(req, res, next) {
  const removed = Book.remove(Number(req.params.id));
  if (!removed) return next(createError(404, "Book not found"));
  res.json({ success: true, data: { message: "Book deleted" } });
}
module.exports = { getAllBooks, getBookById, createBook, updateBook, deleteBook };
JAVASCRIPT
// routes/books.js
const express = require("express");
const router = express.Router();
const ctrl = require("../controllers/bookController");
const { validateBook } = require("../middleware/validate");
router.get("/", ctrl.getAllBooks);
router.get("/:id", ctrl.getBookById);
router.post("/", validateBook, ctrl.createBook);
router.put("/:id", validateBook, ctrl.updateBook);
router.delete("/:id", ctrl.deleteBook);
module.exports = router;
JAVASCRIPT
// app.js
const express = require("express");
const booksRoute = require("./routes/books");
const { errorHandler } = require("./middleware/errorHandler");
const app = express();
app.use(express.json());
app.use("/api/books", booksRoute);
app.use(errorHandler);
app.listen(3000, () => console.log("Server running on port 3000"));
BASH
# Start and Test
node app.js
curl -X POST -H "Content-Type: application/json" \
  -d '{"title":"Clean Code","author":"Robert","year":2008}' \
  localhost:3000/api/books
curl localhost:3000/api/books/1
curl -X DELETE localhost:3000/api/books/1


❓ أسئلة شائعة

س لماذا يتم الفصل بين وحدات التحكم والمسارات؟
ج فصل الاهتمامات — تحدد المسارات تعيينات المسارات، بينما تركز وحدات التحكم على المنطق التشغيلي. ومن خلال فصل هذين العنصرين، يمكن تعديلهما واختبارهما بشكل مستقل.
س كيف يمكنني اختبار واجهة برمجة التطبيقات (API)؟
ج استخدم curl في سطر الأوامر، أو Postman، أو المكون الإضافي Thunder Client لـ VS Code للحصول على واجهة رسومية، ومكتبة supertest للاختبار الآلي.
س في أي طبقة يجب أن يتم التحقق من صحة البيانات؟
ج يجب أن يتم ذلك في طبقة البرمجيات الوسيطة لاعتراض البيانات غير الصحيحة قبل وصول الطلب إلى وحدة التحكم، مما يضمن بقاء منطق وحدة التحكم خالياً من الأخطاء.
س كيف ينبغي التعامل مع الموارد التي يتعذر العثور عليها؟
ج إرجاع رمز الحالة 404 مع تنسيق الخطأ المعياري { success: false, error: { code: 404, message: "Book not found" } }.
س هل ينبغي عليّ استخدام TypeScript في مشروعي؟
ج تُعد لغة JavaScript كافية للمشاريع التدريبية الصغيرة؛ أما بالنسبة للمشاريع الإنتاجية الكبيرة، فيُوصى باستخدام TypeScript، لأنها توفر أمان الأنواع ودعمًا أفضل من بيئة التطوير المتكاملة (IDE).
س هل ستُفقد البيانات المخزنة في الذاكرة بعد إعادة التشغيل؟
ج نعم، يتم تخزين المصفوفات في ذاكرة العملية، لذا يتم مسح البيانات عند إعادة تشغيل الخدمة. في بيئة الإنتاج، ينبغي عليك استخدام قاعدة بيانات (مثل MongoDB أو PostgreSQL، إلخ).
س ما الفرق بين PUT و PATCH؟
ج تتطلب PUT تقديم المورد بالكامل من أجل استبداله بالكامل، بينما ترسل PATCH فقط الحقول التي تحتاج إلى تعديل من أجل تحديث جزئي. في هذا الدرس، سنستخدم PUT لتبسيط الأمور.

📖 ملخص


📝 تمارين

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

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

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

100%