Node.js

بناء REST API في Node.js — دليل شامل 2026

📅 2026-11-08⏱ 10 دقائق قراءة
في المقال السابق، تعلمت التوجيه و Middleware. الآن سنبني **REST API** كاملاً — وهو ما يربط الـ Frontend بالـ Backend. في هذا الدليل العملي، سنأخذك خطوة بخطوة لبناء REST API احترافي، مع تمارين وحلول. ## ما هو REST API؟ **REST** = **Representational State Transfer**. **REST API** هي طريقة لتصميم واجهات برمجية تعتمد على: - **HTTP Methods:** GET، POST، PUT، DELETE. - **Resources:** كيانات (مستخدم، منتج، مقال). - **URLs واضحة:** `/users`، `/users/1`. - **Status Codes:** 200، 201، 404، 500. ## مبادئ REST <table> <thead> <tr> <th>المبدأ</th> <th>الشرح</th> </tr> </thead> <tbody> <tr> <td><strong>Client-Server</strong></td> <td>فصل الواجهة عن البيانات</td> </tr> <tr> <td><strong>Stateless</strong></td> <td>كل طلب مستقل</td> </tr> <tr> <td><strong>Cacheable</strong></td> <td>يمكن تخزين الاستجابات</td> </tr> <tr> <td><strong>Uniform Interface</strong></td> <td>واجهة موحدة</td> </tr> <tr> <td><strong>Layered System</strong></td> <td>طبقات متعددة</td> </tr> </tbody> </table> ## تصميم REST API ### القواعد الذهبية: <table> <thead> <tr> <th>القاعدة</th> <th>مثال صحيح</th> <th>مثال خاطئ</th> </tr> </thead> <tbody> <tr> <td><strong>استخدم الأسماء (جمع)</strong></td> <td><code>/users</code></td> <td><code>/getUser</code></td> </tr> <tr> <td><strong>الأسماء وليس الأفعال</strong></td> <td><code>GET /users</code></td> <td><code>/getUsers</code></td> </tr> <tr> <td><strong>معرفات في URL</strong></td> <td><code>/users/1</code></td> <td><code>/users?id=1</code></td> </tr> <tr> <td><strong>العلاقات المتداخلة</strong></td> <td><code>/users/1/posts</code></td> <td><code>/getUserPosts</code></td> </tr> <tr> <td><strong>أحرف صغيرة</strong></td> <td><code>/blog-posts</code></td> <td><code>/BlogPosts</code></td> </tr> </tbody> </table> ## مثال: API للمستخدمين ### جدول المسارات <table> <thead> <tr> <th>Method</th> <th>URL</th> <th>الوظيفة</th> </tr> </thead> <tbody> <tr> <td><strong>GET</strong></td> <td><code>/api/users</code></td> <td>جلب كل المستخدمين</td> </tr> <tr> <td><strong>GET</strong></td> <td><code>/api/users/:id</code></td> <td>جلب مستخدم واحد</td> </tr> <tr> <td><strong>POST</strong></td> <td><code>/api/users</code></td> <td>إضافة مستخدم</td> </tr> <tr> <td><strong>PUT</strong></td> <td><code>/api/users/:id</code></td> <td>تحديث مستخدم</td> </tr> <tr> <td><strong>DELETE</strong></td> <td><code>/api/users/:id</code></td> <td>حذف مستخدم</td> </tr> </tbody> </table> ## بنية المشروع الاحترافية ``` rest-api/ ├── src/ │ ├── controllers/ │ │ └── userController.js │ ├── routes/ │ │ └── userRoutes.js │ ├── models/ │ │ └── User.js │ ├── middleware/ │ │ ├── auth.js │ │ ├── validate.js │ │ └── errorHandler.js │ ├── utils/ │ │ └── ApiError.js │ └── app.js ├── server.js ├── .env ├── .gitignore └── package.json ``` ## الخطوة 1: إعداد المشروع ```bash mkdir rest-api cd rest-api npm init -y npm install express cors dotenv npm install -D nodemon ``` ## الخطوة 2: `package.json` ```json { "name": "rest-api", "version": "1.0.0", "scripts": { "start": "node server.js", "dev": "nodemon server.js" }, "type": "commonjs" } ``` ## الخطوة 3: `.env` ``` PORT=3000 NODE_ENV=development ``` ## الخطوة 4: `src/utils/ApiError.js` ```javascript class ApiError extends Error { constructor(statusCode, message) { super(message); this.statusCode = statusCode; this.isOperational = true; Error.captureStackTrace(this, this.constructor); } } module.exports = ApiError; ``` ## الخطوة 5: `src/utils/ApiResponse.js` ```javascript class ApiResponse { constructor(statusCode, data, message = "Success") { this.statusCode = statusCode; this.data = data; this.message = message; this.success = statusCode < 400; } } module.exports = ApiResponse; ``` ## الخطوة 6: `src/models/User.js` (في الذاكرة) ```javascript // قاعدة بيانات مؤقتة (سنستبدلها بـ MongoDB لاحقاً) let users = [ { id: 1, name: "أحمد", email: "[email protected]", age: 25 }, { id: 2, name: "محمد", email: "[email protected]", age: 30 }, ]; let nextId = 3; class User { static findAll() { return users; } static findById(id) { return users.find((u) => u.id === parseInt(id)); } static findByEmail(email) { return users.find((u) => u.email === email); } static create(data) { const user = { id: nextId++, ...data }; users.push(user); return user; } static update(id, data) { const user = users.find((u) => u.id === parseInt(id)); if (!user) return null; Object.assign(user, data); return user; } static delete(id) { const index = users.findIndex((u) => u.id === parseInt(id)); if (index === -1) return false; users.splice(index, 1); return true; } } module.exports = User; ``` ## الخطوة 7: `src/controllers/userController.js` ```javascript const User = require("../models/User"); const ApiError = require("../utils/ApiError"); const ApiResponse = require("../utils/ApiResponse"); // جلب كل المستخدمين exports.getAllUsers = (req, res) => { const users = User.findAll(); res.status(200).json(new ApiResponse(200, users, "تم جلب المستخدمين")); }; // جلب مستخدم واحد exports.getUserById = (req, res, next) => { const user = User.findById(req.params.id); if (!user) { return next(new ApiError(404, "المستخدم غير موجود")); } res.status(200).json(new ApiResponse(200, user)); }; // إضافة مستخدم exports.createUser = (req, res, next) => { const { name, email, age } = req.body; // التحقق من وجود البريد if (User.findByEmail(email)) { return next(new ApiError(400, "البريد مستخدم بالفعل")); } const user = User.create({ name, email, age }); res.status(201).json(new ApiResponse(201, user, "تم إنشاء المستخدم")); }; // تحديث مستخدم exports.updateUser = (req, res, next) => { const user = User.update(req.params.id, req.body); if (!user) { return next(new ApiError(404, "المستخدم غير موجود")); } res.status(200).json(new ApiResponse(200, user, "تم التحديث")); }; // حذف مستخدم exports.deleteUser = (req, res, next) => { const deleted = User.delete(req.params.id); if (!deleted) { return next(new ApiError(404, "المستخدم غير موجود")); } res.status(200).json(new ApiResponse(200, null, "تم الحذف")); }; ``` ## الخطوة 8: `src/routes/userRoutes.js` ```javascript const express = require("express"); const router = express.Router(); const userController = require("../controllers/userController"); const validate = require("../middleware/validate"); // التحقق من البيانات const validateUser = validate({ name: { required: true, minLength: 2 }, email: { required: true, email: true }, age: { required: false, min: 18, max: 100 }, }); router.get("/", userController.getAllUsers); router.get("/:id", userController.getUserById); router.post("/", validateUser, userController.createUser); router.put("/:id", userController.updateUser); router.delete("/:id", userController.deleteUser); module.exports = router; ``` ## الخطوة 9: `src/middleware/validate.js` ```javascript const ApiError = require("../utils/ApiError"); function validate(rules) { return (req, res, next) => { const errors = []; for (const [field, rule] of Object.entries(rules)) { const value = req.body[field]; if (rule.required && !value) { errors.push(`${field} مطلوب`); continue; } if (value) { if (rule.minLength && value.length < rule.minLength) { errors.push(`${field} قصير جداً (الحد الأدنى ${rule.minLength})`); } if (rule.maxLength && value.length > rule.maxLength) { errors.push(`${field} طويل جداً`); } if (rule.email && !value.includes("@")) { errors.push(`${field} غير صحيح`); } if (rule.min !== undefined && value < rule.min) { errors.push(`${field} يجب أن يكون أكبر من ${rule.min}`); } if (rule.max !== undefined && value > rule.max) { errors.push(`${field} يجب أن يكون أقل من ${rule.max}`); } } } if (errors.length > 0) { return next(new ApiError(400, errors.join(", "))); } next(); }; } module.exports = validate; ``` ## الخطوة 10: `src/middleware/errorHandler.js` ```javascript const ApiError = require("../utils/ApiError"); // 404 Handler const notFound = (req, res, next) => { next(new ApiError(404, `المسار غير موجود: ${req.originalUrl}`)); }; // Error Handler const errorHandler = (err, req, res, next) => { const statusCode = err.statusCode || 500; const message = err.message || "خطأ في السيرفر"; // تسجيل الخطأ if (process.env.NODE_ENV === "development") { console.error("❌ خطأ:", err); } res.status(statusCode).json({ success: false, statusCode, message, ...(process.env.NODE_ENV === "development" && { stack: err.stack }), }); }; module.exports = { notFound, errorHandler }; ``` ## الخطوة 11: `src/app.js` ```javascript const express = require("express"); const cors = require("cors"); const { notFound, errorHandler } = require("./middleware/errorHandler"); const app = express(); // Middleware عام app.use(cors()); app.use(express.json()); app.use(express.urlencoded({ extended: true })); // Logger app.use((req, res, next) => { console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`); next(); }); // Health check app.get("/api/health", (req, res) => { res.json({ status: "OK", timestamp: new Date().toISOString(), uptime: process.uptime(), }); }); // Routes app.use("/api/users", require("./routes/userRoutes")); // 404 و Error Handler (في النهاية) app.use(notFound); app.use(errorHandler); module.exports = app; ``` ## الخطوة 12: `server.js` ```javascript require("dotenv").config(); const app = require("./src/app"); const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`🚀 السيرفر يعمل على http://localhost:${PORT}`); console.log(`📋 Health: http://localhost:${PORT}/api/health`); console.log(`👥 Users: http://localhost:${PORT}/api/users`); }); ``` ## اختبار API ### باستخدام `curl`: ```bash # جلب كل المستخدمين curl http://localhost:3000/api/users # جلب مستخدم واحد curl http://localhost:3000/api/users/1 # إضافة مستخدم curl -X POST http://localhost:3000/api/users \ -H "Content-Type: application/json" \ -d '{"name":"علي","email":"[email protected]","age":28}' # تحديث مستخدم curl -X PUT http://localhost:3000/api/users/1 \ -H "Content-Type: application/json" \ -d '{"name":"أحمد محمد"}' # حذف مستخدم curl -X DELETE http://localhost:3000/api/users/1 ``` ### باستخدام Postman: 1. حمّل Postman من [postman.com](https://www.postman.com/) 2. جرّب الطلبات بنفس الطريقة. 3. احفظ المجموعة (Collection) لمشاركتها. ## شكل الاستجابات ### نجاح: ```json { "statusCode": 200, "data": { "id": 1, "name": "أحمد" }, "message": "Success", "success": true } ``` ### خطأ: ```json { "success": false, "statusCode": 404, "message": "المستخدم غير موجود" } ``` ## تمارين عملية ### تمرين 1: API للمقالات أنشئ API كامل للمقالات (CRUD). **الحل:** ```javascript // src/models/Post.js let posts = []; let nextId = 1; class Post { static findAll() { return posts; } static findById(id) { return posts.find((p) => p.id === parseInt(id)); } static create(data) { const post = { id: nextId++, ...data, createdAt: new Date() }; posts.push(post); return post; } static update(id, data) { const post = this.findById(id); if (!post) return null; Object.assign(post, data, { updatedAt: new Date() }); return post; } static delete(id) { const index = posts.findIndex((p) => p.id === parseInt(id)); if (index === -1) return false; posts.splice(index, 1); return true; } } module.exports = Post; ``` ### تمرين 2: التحقق من البيانات أضف تحققاً لمقالات. **الحل:** ```javascript const validatePost = validate({ title: { required: true, minLength: 5, maxLength: 200 }, content: { required: true, minLength: 20 }, }); ``` ### تمرين 3: البحث والتصفية أضف `?q=` و `?sort=` للمستخدمين. **الحل:** ```javascript exports.getAllUsers = (req, res) => { let users = User.findAll(); const { q, sort } = req.query; if (q) { users = users.filter((u) => u.name.toLowerCase().includes(q.toLowerCase()) ); } if (sort === "name") { users = users.sort((a, b) => a.name.localeCompare(b.name)); } res.json(new ApiResponse(200, users)); }; ``` ### تمرين 4: Pagination أضف `?page=1&limit=10`. **الحل:** ```javascript const { page = 1, limit = 10 } = req.query; const start = (page - 1) * limit; const paginated = users.slice(start, start + parseInt(limit)); res.json(new ApiResponse(200, { users: paginated, total: users.length, page: parseInt(page), totalPages: Math.ceil(users.length / limit), })); ``` ### تمرين 5: رفع الملفات أضف رفع صورة المستخدم. **الحل:** ```bash npm install multer ``` ```javascript const multer = require("multer"); const upload = multer({ dest: "uploads/" }); router.post("/:id/avatar", upload.single("avatar"), (req, res) => { res.json({ file: req.file }); }); ``` ### تمرين 6: Middleware للمصادقة أضف JWT للمصادقة. **الحل:** ```bash npm install jsonwebtoken bcrypt ``` ```javascript function auth(req, res, next) { const token = req.headers.authorization?.split(" ")[1]; if (!token) return next(new ApiError(401, "غير مصرح")); try { req.user = jwt.verify(token, process.env.JWT_SECRET); next(); } catch { next(new ApiError(401, "توكن غير صالح")); } } router.get("/profile", auth, (req, res) => { res.json(req.user); }); ``` ### تمرين 7: Rate Limiting أضف حداً للطلبات. **الحل:** ```bash npm install express-rate-limit ``` ```javascript const rateLimit = require("express-rate-limit"); const limiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 100, }); app.use("/api", limiter); ``` ### تمرين 8: API كامل ابنِ API كامل مع كل الميزات. **الحل:** (راجع المثال الكامل أعلاه) ## حل المشاكل الشائعة ### 🔴 المشكلة 1: CORS Error **الحل:** ```javascript const cors = require("cors"); app.use(cors()); ``` ### 🔴 المشكلة 2: `req.body` فارغ **الحل:** ```javascript app.use(express.json()); ``` ### 🔴 المشكلة 3: 500 Internal Server Error **السبب:** خطأ غير معالج. **الحل:** أضف `try/catch` أو استخدم Error Handler. ### 🔴 المشكلة 4: 404 لمسار موجود **السبب:** 404 Handler قبل المسارات. **الحل:** ضعه **بعد** كل المسارات. ### 🔴 المشكلة 5: Status Code خاطئ **الحل:** - **200:** نجاح. - **201:** إنشاء. - **204:** حذف. - **400:** طلب خاطئ. - **401:** غير مصرح. - **404:** غير موجود. - **500:** خطأ في السيرفر. ## جدول الاستجابات <table> <thead> <tr> <th>الحالة</th> <th>الكود</th> <th>الاستخدام</th> </tr> </thead> <tbody> <tr> <td>نجاح</td> <td>200</td> <td>GET, PUT</td> </tr> <tr> <td>إنشاء</td> <td>201</td> <td>POST</td> </tr> <tr> <td>حذف</td> <td>204</td> <td>DELETE</td> </tr> <tr> <td>طلب خاطئ</td> <td>400</td> <td>بيانات غير صالحة</td> </tr> <tr> <td>غير مصرح</td> <td>401</td> <td>توكن مفقود</td> </tr> <tr> <td>ممنوع</td> <td>403</td> <td>صلاحيات غير كافية</td> </tr> <tr> <td>غير موجود</td> <td>404</td> <td>مسار/مورد</td> </tr> <tr> <td>خطأ في السيرفر</td> <td>500</td> <td>خطأ داخلي</td> </tr> </tbody> </table> ## قائمة تحقق نهائية <table> <thead> <tr> <th>المهمة</th> <th>الحالة</th> </tr> </thead> <tbody> <tr> <td>فهم مبادئ REST</td> <td>⬜</td> </tr> <tr> <td>تنظيم المشروع</td> <td>⬜</td> </tr> <tr> <td>فصل Controllers و Routes</td> <td>⬜</td> </tr> <tr> <td>التحقق من البيانات</td> <td>⬜</td> </tr> <tr> <td>معالجة الأخطاء</td> <td>⬜</td> </tr> <tr> <td>اختبار API</td> <td>⬜</td> </tr> <tr> <td>حل التمارين الثمانية</td> <td>⬜</td> </tr> </tbody> </table> ## ماذا بعد هذا المقال؟ الآن بعد أن أتقنت REST API، أنت جاهز للمقال التالي: 1. **MongoDB** — قاعدة بيانات حقيقية. 2. **المصادقة (JWT)** — تسجيل الدخول. 3. **مشروع متكامل** — API كامل مع قاعدة بيانات. ## الخلاصة في هذا المقال، تعلمت: - ✅ ما هو REST API. - ✅ مبادئ REST. - ✅ تنظيم المشروع الاحترافي. - ✅ Controllers و Routes. - ✅ التحقق من البيانات. - ✅ معالجة الأخطاء. - ✅ بناء API كامل. - ✅ حل 8 تمارين عملية. **تذكر:** REST API هو **الواجهة** التي تربط Frontend بـ Backend. أتقنه جيداً.