Node.js📅 2026-11-08⏱ 10 دقائق قراءة🟢 مقال 8 من 10

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

دليل عملي مفصل لبناء REST API احترافي — المبادئ، التنظيم، التحقق، والاختبار، مع تمارين وحلول

في المقال السابق، تعلمت التوجيه و 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

المبدأالشرح
Client-Serverفصل الواجهة عن البيانات
Statelessكل طلب مستقل
Cacheableيمكن تخزين الاستجابات
Uniform Interfaceواجهة موحدة
Layered Systemطبقات متعددة

تصميم REST API

القواعد الذهبية:

القاعدةمثال صحيحمثال خاطئ
استخدم الأسماء (جمع)/users/getUser
الأسماء وليس الأفعالGET /users/getUsers
معرفات في URL/users/1/users?id=1
العلاقات المتداخلة/users/1/posts/getUserPosts
أحرف صغيرة/blog-posts/BlogPosts

مثال: API للمستخدمين

جدول المسارات

MethodURLالوظيفة
GET/api/usersجلب كل المستخدمين
GET/api/users/:idجلب مستخدم واحد
POST/api/usersإضافة مستخدم
PUT/api/users/:idتحديث مستخدم
DELETE/api/users/:idحذف مستخدم

بنية المشروع الاحترافية

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: إعداد المشروع

mkdir rest-api
cd rest-api
npm init -y
npm install express cors dotenv
npm install -D nodemon

الخطوة 2: package.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

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

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 (في الذاكرة)

// قاعدة بيانات مؤقتة (سنستبدلها بـ 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

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

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

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

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

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

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:

# جلب كل المستخدمين
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
  2. جرّب الطلبات بنفس الطريقة.
  3. احفظ المجموعة (Collection) لمشاركتها.

شكل الاستجابات

نجاح:

{
  "statusCode": 200,
  "data": { "id": 1, "name": "أحمد" },
  "message": "Success",
  "success": true
}

خطأ:

{
  "success": false,
  "statusCode": 404,
  "message": "المستخدم غير موجود"
}

تمارين عملية

تمرين 1: API للمقالات

أنشئ API كامل للمقالات (CRUD).

الحل:

// 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: التحقق من البيانات

أضف تحققاً لمقالات.

الحل:

const validatePost = validate({
  title: { required: true, minLength: 5, maxLength: 200 },
  content: { required: true, minLength: 20 },
});

تمرين 3: البحث والتصفية

أضف ?q= و ?sort= للمستخدمين.

الحل:

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.

الحل:

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: رفع الملفات

أضف رفع صورة المستخدم.

الحل:

npm install multer
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 للمصادقة.

الحل:

npm install jsonwebtoken bcrypt
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

أضف حداً للطلبات.

الحل:

npm install express-rate-limit
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

الحل:

const cors = require("cors");
app.use(cors());

🔴 المشكلة 2: req.body فارغ

الحل:

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: خطأ في السيرفر.

جدول الاستجابات

الحالةالكودالاستخدام
نجاح200GET, PUT
إنشاء201POST
حذف204DELETE
طلب خاطئ400بيانات غير صالحة
غير مصرح401توكن مفقود
ممنوع403صلاحيات غير كافية
غير موجود404مسار/مورد
خطأ في السيرفر500خطأ داخلي

قائمة تحقق نهائية

المهمةالحالة
فهم مبادئ REST⬜
تنظيم المشروع⬜
فصل Controllers و Routes⬜
التحقق من البيانات⬜
معالجة الأخطاء⬜
اختبار API⬜
حل التمارين الثمانية⬜

ماذا بعد هذا المقال؟

الآن بعد أن أتقنت REST API، أنت جاهز للمقال التالي:

  1. MongoDB — قاعدة بيانات حقيقية.
  2. المصادقة (JWT) — تسجيل الدخول.
  3. مشروع متكامل — API كامل مع قاعدة بيانات.

الخلاصة

في هذا المقال، تعلمت:

  • ✅ ما هو REST API.
  • ✅ مبادئ REST.
  • ✅ تنظيم المشروع الاحترافي.
  • ✅ Controllers و Routes.
  • ✅ التحقق من البيانات.
  • ✅ معالجة الأخطاء.
  • ✅ بناء API كامل.
  • ✅ حل 8 تمارين عملية.

تذكر: REST API هو الواجهة التي تربط Frontend بـ Backend. أتقنه جيداً.

📚 مقالات ذات صلة

Node.js

مشروع Node.js متكامل — API مع مصادقة JWT 2026

مشروع عملي شامل لبناء API متكامل بـ Node.js و Express و MongoDB — مع مصادقة JWT، رفع الملفات، والنشر

Node.js

MongoDB مع Node.js — دليل شامل 2026

دليل عملي مفصل لاستخدام MongoDB مع Node.js — Mongoose، Schema، CRUD، العلاقات، مع تمارين وحلول

Node.js

التوجيه و Middleware في Express.js — دليل شامل 2026

دليل عملي مفصل للتوجيه (Routing) و Middleware في Express.js — Router، تنظيم المسارات، مع تمارين وحلول