Node.js: مشروع API (الجزء 1)
آخر تحديث: 2026-08-26
1. إطلاق المشروع: اليوم الأول لأليس
تلقت أليس وفريقها طلبًا جديدًا — وهو إنشاء واجهة برمجة تطبيقات (API) لإدارة المهام الجماعية. في اليوم الأول، قرروا البدء بإعداد إطار عمل المشروع ووحدة المصادقة للمستخدمين. قالت أليس: «المصادقة هي أساس كل شيء. فبدون المصادقة، لن يكون لبقية نظام إدارة المهام أي معنى».
- تُعدّ مرحلة بدء المشروع وتصميم بنية الدلائل الخطوات الأولى في مجال الهندسة.
- يشكل تصميم نموذج Mongoose الأساس لطبقة البيانات
- يُعد الجمع بين JWT وbcrypt الحل السائد للمصادقة في Node.js
- تتألف واجهة برمجة التطبيقات (API) الخاصة بالتسجيل/تسجيل الدخول من نقطتي نهاية أساسيتين في وحدة المصادقة
- تعمل البرمجيات الوسيطة
authعلى حماية المسارات التي تتطلب المصادقة
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
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) الذي تم إرجاعه، ثم استخدم هذا الرمز المميز للوصول إلى مسار محمي للتحقق من أن عملية المصادقة تعمل بشكل صحيح.
- س: لماذا نحتاج إلى المصادقة أولاً؟ ج: المصادقة شرط أساسي لتطبيق منطق الأعمال؛ فبدون هوية، يستحيل تحديد ملكية البيانات والتحكم في أذونات الوصول، كما أن جميع عمليات CRUD اللاحقة تعتمد على المصادقة.
- س: هل تخزين كلمات المرور آمن؟ ج: يستخدم bcrypt التجزئة التكيفية مع «سولت» (salt)، وهي طريقة تفوق بكثير النص العادي أو خوارزميات MD5/SHA. وبإجراء 10 جولات، تبلغ التكلفة حوالي 100 مللي ثانية لكل عملية، مما يحقق توازنًا بين الأمان والأداء.
- س: كيف ينبغي إدارة سر JWT؟ ج: استخدم ملف .env في بيئة التطوير، واستخدم متغيرات البيئة أو خدمة إدارة الأسرار (مثل AWS Secrets Manager) في بيئة الإنتاج؛ ولا تقم أبدًا بكتابته بشكل ثابت في الكود.
- س: كيف يمكنني اختبار عملية التسجيل/تسجيل الدخول؟ ج: استخدم Postman أو curl لإرسال طلب POST إلى
/api/auth/registerو/api/auth/login، وتحقق من الرمز المميز الذي تم إرجاعه. - س: كيف ينبغي تنظيم هيكل المشروع؟ ج: قسّمه إلى مكونات MVC: النماذج (البيانات)، والمسارات، والبرمجيات الوسيطة، والإعدادات، للحفاظ على فصل الاهتمامات.
- س: أيهما أفضل، bcrypt أم crypto؟ ج: تم تصميم bcrypt خصيصًا لكلمات المرور، وهو يتضمن قيم «سولت» مدمجة وتكلفة تكييفية، مما يجعله أكثر ملاءمة لتجزئة كلمات المرور مقارنة بسلسلة SHA الموجودة في حزمة crypto.
📖 ملخص
- إطلاق المشروع: المفاهيم الأساسية وكيفية استخدام «أليس» في اليوم الأول
- المفاهيم الأساسية واستخدامات تهيئة المشروع وهياكل الدلائل
- المفاهيم الأساسية وتطبيقات تصميم نماذج Mongoose
- المفاهيم الأساسية لوحدة مصادقة المستخدم وكيفية استخدامها
- المفاهيم الأساسية واستخدام الملف الرئيسي app.js وملف التكوين .env
- مثال شامل: المفاهيم الأساسية وكيفية استخدام عملية تهيئة المشروع بأكملها
📝 تمارين
- قم بتهيئة المشروع وفقًا لهيكل المجلدات، وقم بتثبيت جميع التبعيات، وتأكد من أن
npm run devيمكنه تشغيل الخادم. - قم بإنشاء نموذجين في Mongoose، هما
UserوTask، واستخدم Postman لاختبار واجهات برمجة التطبيقات (API) الخاصة بالتسجيل وتسجيل الدخول. - اكتب برنامجًا وسيطًا للمصادقة واختبر نقطة النهاية
/api/auth/me: يجب أن تُرجع خطأً برقم 401 في حالة عدم توفير رمز مصادقة، وأن تُرجع معلومات المستخدم في حالة توفير رمز مصادقة صالح. - قم بتكوين JWT_SECRET و MONGO_URI في
.env، وتجاهل الملف.envالموجود في.gitignore.