Node.js: مشروع API (الجزء 1)

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

1. إطلاق المشروع: اليوم الأول لأليس

تلقت أليس وفريقها طلبًا جديدًا — وهو إنشاء واجهة برمجة تطبيقات (API) لإدارة المهام الجماعية. في اليوم الأول، قرروا البدء بإعداد إطار عمل المشروع ووحدة المصادقة للمستخدمين. قالت أليس: «المصادقة هي أساس كل شيء. فبدون المصادقة، لن يكون لبقية نظام إدارة المهام أي معنى».



2. تهيئة المشروع وهيكل المجلدات

▶ مثال:(1) تهيئة مشروع Express

BASH
mkdir task-manager-api && cd task-manager-api
npm init -y
npm install express mongoose bcryptjs jsonwebtoken dotenv cors helmet
npm install --save-dev nodemon

▶ مثال:(2) تصميم بنية الدليل

TEXT 📖 للعرض فقط
task-manager-api/
├── src/
│   ├── config/
│   │   └── db.js
│   ├── middleware/
│   │   └── auth.js
│   ├── models/
│   │   ├── User.js
│   │   └── Task.js
│   ├── routes/
│   │   ├── auth.js
│   │   └── tasks.js
│   ├── validators/
│   │   └── authValidator.js
│   └── app.js
├── .env
├── .gitignore
├── package.json
└── server.js
الدليل/الملف الوصف
src/config/ اتصال قاعدة البيانات، ومتغيرات البيئة، والإعدادات الأخرى
src/middleware/ برمجيات وسيطة للمصادقة ومعالجة الأخطاء، وما إلى ذلك
src/models/ تعريفات المخطط والنموذج في Mongoose
src/routes/ وحدات التوجيه، مصنفة حسب الوظيفة
src/validators/ منطق التحقق من صحة معلمات الطلب
src/app.js ملف التطبيق الرئيسي لـ Express
server.js ملف الإدخال، بدء تشغيل الخادم

▶ مثال: ملف الدخول server.js

JAVASCRIPT
require('dotenv').config();
const app = require('./src/app');
const connectDB = require('./src/config/db');

connectDB();

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

▶ مثال: تكوين اتصال قاعدة البيانات

JAVASCRIPT
const mongoose = require('mongoose');

const connectDB = async () => {
  try {
    await mongoose.connect(process.env.MONGO_URI);
    console.log('MongoDB connected');
  } catch (err) {
    console.error('MongoDB connection error:', err.message);
    process.exit(1);
  }
};

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

3. تصميم نموذج Mongoose

(1) نموذج المستخدم

الحقل النوع إلزامي الوصف
username سلسلة نعم اسم المستخدم، فهرس فريد
email سلسلة نعم البريد الإلكتروني، فهرس فريد
password سلسلة نعم كلمة مرور مشفرة باستخدام bcrypt
role سلسلة لا الدور: user (افتراضي) / admin
createdAt التاريخ تلقائي تاريخ الإنشاء

(2) نموذج المهمة

الحقل النوع إلزامي الوصف
title سلسلة نعم عنوان المهمة
description سلسلة لا تفاصيل المهمة
status سلسلة لا pending (الافتراضي) / in-progress / completed
priority سلسلة لا low (الافتراضي) / medium / high
assignedTo ObjectId نعم مستخدم مُعيَّن، مرتبط بـ «المستخدم»
dueDate التاريخ رقم الموعد النهائي
createdAt التاريخ تلقائي تاريخ الإنشاء
updatedAt التاريخ تلقائي آخر تحديث

▶ مثال: تعريف نموذج المستخدم

JAVASCRIPT
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');

const userSchema = new mongoose.Schema({
  username: { type: String, required: true, unique: true, trim: true },
  email: { type: String, required: true, unique: true, lowercase: true },
  password: { type: String, required: true, minlength: 6 },
  role: { type: String, enum: ['user', 'admin'], default: 'user' }
}, { timestamps: true });

userSchema.pre('save', async function (next) {
  if (!this.isModified('password')) return next();
  this.password = await bcrypt.hash(this.password, 10);
  next();
});

userSchema.methods.comparePassword = function (candidate) {
  return bcrypt.compare(candidate, this.password);
};

module.exports = mongoose.model('User', userSchema);
▶ جرّب الكود

▶ مثال: تعريف نموذج المهمة

JAVASCRIPT
const mongoose = require('mongoose');

const taskSchema = new mongoose.Schema({
  title: { type: String, required: true, trim: true },
  description: { type: String, default: '' },
  status: { type: String, enum: ['pending', 'in-progress', 'completed'], default: 'pending' },
  priority: { type: String, enum: ['low', 'medium', 'high'], default: 'low' },
  assignedTo: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
  dueDate: { type: Date }
}, { timestamps: true });

module.exports = mongoose.model('Task', taskSchema);
▶ جرّب الكود

4. وحدة مصادقة المستخدم

▶ مثال:(1) كيف تعمل مصادقة JWT

100%
graph TD
    A[User Registration/Log In] --> B[Server Authentication Credentials]
    B --> C[Generate JWT Token]
    C --> D[Back Token To the client]
    D --> E[Client-side Token Request]
    E --> F[auth Middleware Validation Token]
    F -->|Valid| G[Forward to the routing processor]
    F -->|Invalid| H[Back 401 Error]

(2) تصميم نقاط نهاية واجهة برمجة التطبيقات (API) الخاصة بالمصادقة

الطريقة المسار الوصف المصادقة مطلوبة
منشور /api/auth/register تسجيل المستخدم لا
منشور /api/auth/login تسجيل دخول المستخدم لا
GET /api/auth/me الحصول على المستخدم الحالي نعم

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

JAVASCRIPT
const router = require('express').Router();
const jwt = require('jsonwebtoken');
const User = require('../models/User');

const generateToken = (id) => jwt.sign({ id }, process.env.JWT_SECRET, { expiresIn: '7d' });

router.post('/register', async (req, res, next) => {
  try {
    const { username, email, password } = req.body;
    const user = await User.create({ username, email, password });
    res.status(201).json({ token: generateToken(user._id), user: { id: user._id, username, email, role: user.role } });
  } catch (err) {
    next(err);
  }
});

router.post('/login', async (req, res, next) => {
  try {
    const { email, password } = req.body;
    const user = await User.findOne({ email });
    if (!user || !(await user.comparePassword(password))) {
      return res.status(401).json({ message: 'Invalid credentials' });
    }
    res.json({ token: generateToken(user._id), user: { id: user._id, username: user.username, email, role: user.role } });
  } catch (err) {
    next(err);
  }
});

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

▶ مثال: برمجيات وسيطة للتوثيق

JAVASCRIPT
const jwt = require('jsonwebtoken');
const User = require('../models/User');

module.exports = async (req, res, next) => {
  const header = req.headers.authorization;
  if (!header || !header.startsWith('Bearer ')) {
    return res.status(401).json({ message: 'No token provided' });
  }
  try {
    const decoded = jwt.verify(header.split(' ')[1], process.env.JWT_SECRET);
    req.user = await User.findById(decoded.id).select('-password');
    if (!req.user) return res.status(401).json({ message: 'User not found' });
    next();
  } catch {
    res.status(401).json({ message: 'Invalid token' });
  }
};
▶ جرّب الكود

▶ مثال: الحصول على مسار المستخدم الحالي

JAVASCRIPT
const auth = require('../middleware/auth');

router.get('/me', auth, async (req, res) => {
  res.json({ user: { id: req.user._id, username: req.user.username, email: req.user.email, role: req.user.role } });
});
▶ جرّب الكود

5. الملف الرئيسي app.js وملف التكوين .env

▶ مثال: دمج ملف app.js

JAVASCRIPT
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const authRoutes = require('./routes/auth');

const app = express();

app.use(helmet());
app.use(cors());
app.use(express.json());

app.use('/api/auth', authRoutes);

app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(err.statusCode || 500).json({ message: err.message || 'Server Error' });
});

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

▶ مثال: متغيرات البيئة في ملف .env

TEXT 📖 للعرض فقط
PORT=3000
MONGO_URI=mongodb://localhost:27017/task-manager
JWT_SECRET=your_super_secret_key_change_in_production


6. مثال شامل: عملية تهيئة المشروع بالكامل

يربط الكود التالي جميع الوحدات الأساسية المذكورة أعلاه معًا. وباستخدام حوالي 80 سطرًا من الكود الأساسي، يمكنك تشغيل واجهة برمجة تطبيقات (API) أساسية لإدارة المهام مع ميزة المصادقة:

JAVASCRIPT
// server.js
require('dotenv').config();
const express = require('express');
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const cors = require('cors');
const helmet = require('helmet');

const app = express();
app.use(helmet(), cors(), express.json());

// --- Models ---
const userSchema = new mongoose.Schema({
  username: { type: String, required: true, unique: true },
  email: { type: String, required: true, unique: true },
  password: { type: String, required: true },
  role: { type: String, enum: ['user', 'admin'], default: 'user' }
}, { timestamps: true });
userSchema.pre('save', async function () { if (this.isModified('password')) this.password = await bcrypt.hash(this.password, 10); });
userSchema.methods.comparePassword = function (pw) { return bcrypt.compare(pw, this.password); };
const User = mongoose.model('User', userSchema);

// --- Auth Middleware ---
const auth = async (req, res, next) => {
  try {
    const decoded = jwt.verify(req.headers.authorization?.split(' ')[1], process.env.JWT_SECRET);
    req.user = await User.findById(decoded.id).select('-password');
    next();
  } catch { res.status(401).json({ message: 'Unauthorized' }); }
};

// --- Routes ---
app.post('/api/auth/register', async (req, res) => {
  const user = await User.create(req.body);
  const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '7d' });
  res.status(201).json({ token, user: { id: user._id, username: user.username, role: user.role } });
});
app.post('/api/auth/login', async (req, res) => {
  const user = await User.findOne({ email: req.body.email });
  if (!user || !(await user.comparePassword(req.body.password))) return res.status(401).json({ message: 'Invalid credentials' });
  const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '7d' });
  res.json({ token, user: { id: user._id, username: user.username, role: user.role } });
});
app.get('/api/auth/me', auth, (req, res) => res.json(req.user));

// --- Start ---
mongoose.connect(process.env.MONGO_URI).then(() => {
  app.listen(process.env.PORT || 3000, () => console.log('Server running'));
});


❓ أسئلة شائعة

س كيف ينبغي تصميم بنية مجلدات المشروع؟
ج قم بتنظيمها حسب الوظائف: قم بتخزين نماذج البيانات في models/، والمسارات في routes/، والبرمجيات الوسيطة في middleware/، والإعدادات في config/. ضع نقطة الدخول، app.js، في المجلد الجذر.
س لماذا نستخدم Mongoose بدلاً من برنامج التشغيل الأصلي؟
ج يوفر Mongoose التحقق من صحة المخطط، ووظائف ربط البرامج الوسيطة، وتلميحات الأنواع، ومنشئ الاستعلامات، مما يعزز كفاءة التطوير؛ أما برنامج التشغيل الأصلي فهو أخف وزناً وأكثر ملاءمةً للسيناريوهات البسيطة.
س هل يجب إضافة ملف .env إلى Git؟
ج بالطبع لا. يحتوي ملف .env على معلومات حساسة مثل كلمات مرور قواعد البيانات والمفاتيح، لذا يجب إضافته إلى ملف .gitignore وإدراجه عبر متغيرات البيئة أو من خلال عملية التكامل المستمر (CI) أثناء النشر.
س ما هو الطول المطلوب لسر JWT؟
ج يجب أن يكون سلسلة عشوائية مكونة من 32 حرفًا على الأقل؛ أما في بيئات الإنتاج، فيُوصى باستخدام 64 حرفًا أو أكثر. يمكنك إنشاء واحد باستخدام node -e "console.log(require('crypto').randomBytes(64).toString('hex'))".
س كيف يمكنني اختبار واجهة برمجة التطبيقات (API) الخاصة بالتسجيل؟
ج استخدم Postman أو curl لإرسال طلب POST إلى /api/auth/register، وتحقق من الرمز المميز (token) الذي تم إرجاعه، ثم استخدم هذا الرمز المميز للوصول إلى مسار محمي للتحقق من أن عملية المصادقة تعمل بشكل صحيح.

📖 ملخص

📝 تمارين

  1. قم بتهيئة المشروع وفقًا لهيكل المجلدات، وقم بتثبيت جميع التبعيات، وتأكد من أن npm run dev يمكنه تشغيل الخادم.
  2. قم بإنشاء نموذجين في Mongoose، هما User وTask، واستخدم Postman لاختبار واجهات برمجة التطبيقات (API) الخاصة بالتسجيل وتسجيل الدخول.
  3. اكتب برنامجًا وسيطًا للمصادقة واختبر نقطة النهاية /api/auth/me: يجب أن تُرجع خطأً برقم 401 في حالة عدم توفير رمز مصادقة، وأن تُرجع معلومات المستخدم في حالة توفير رمز مصادقة صالح.
  4. قم بتكوين JWT_SECRET و MONGO_URI في .env، وتجاهل الملف .env الموجود في .gitignore.

Web-Tutorial.com

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

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

100%