دليل شامل لتعلم واستخدام إطار عمل Express.js
Express.js هو إطار عمل ويب مرن ومينيمالي لـ Node.js يوفر مجموعة قوية من الميزات لتطوير تطبيقات الويب وواجهات برمجة التطبيقات (APIs). تم تصميمه ليكون بسيطًا وسهل الاستخدام، مما يجعله اختيارًا شائعًا للمطورين الذين يريدون بناء تطبيقات ويب سريعة وقابلة للتطوير.
Express هو جزء من منظومة MEAN/MERN stack (MongoDB, Express, Angular/React, Node.js) المستخدمة على نطاق واسع لتطوير تطبيقات الويب الكاملة.
قبل البدء في استخدام Express.js، تحتاج إلى تثبيت:
لإنشاء مشروع Express جديد، اتبع الخطوات التالية:
# إنشاء مجلد جديد للمشروع
mkdir my-express-app
cd my-express-app
# تهيئة مشروع npm
npm init -y
# تثبيت Express
npm install express
بعد تثبيت Express، يمكنك إنشاء تطبيق بسيط. قم بإنشاء ملف app.js
في مجلد المشروع:
const express = require('express');
const app = express();
const port = 3000;
// طريق الصفحة الرئيسية
app.get('/', (req, res) => {
res.send('مرحباً بالعالم من Express!');
});
// بدء الخادم
app.listen(port, () => {
console.log(`التطبيق يعمل على المنفذ ${port}`);
});
لتشغيل التطبيق، قم بتنفيذ الأمر التالي:
node app.js
بعد تنفيذ الأمر، يمكنك فتح المتصفح وزيارة http://localhost:3000
لرؤية رسالة "مرحباً بالعالم من Express!".
يمكنك استخدام nodemon لإعادة تشغيل التطبيق تلقائيًا عند تغيير الكود:
# تثبيت nodemon عالمياً
npm install -g nodemon
# تشغيل التطبيق باستخدام nodemon
nodemon app.js
يمكنك أيضاً تثبيت nodemon كتبعية تطوير وإضافة سكريبت في ملف package.json:
"scripts": { "start": "node app.js", "dev": "nodemon app.js" }
ثم يمكنك تشغيل التطبيق في وضع التطوير باستخدام npm run dev
.
تطبيق Express هو كائن من الدالة express()
. يتم استخدامه لإعداد الخادم وتكوين الطرق ومعالجات الوسائط والإعدادات الأخرى.
const express = require('express');
const app = express();
يدعم Express جميع طرق HTTP الشائعة مثل: GET، POST، PUT، DELETE، إلخ.
// طلب GET
app.get('/users', (req, res) => {
res.send('قائمة المستخدمين');
});
// طلب POST
app.post('/users', (req, res) => {
res.send('تم إنشاء مستخدم جديد');
});
// طلب PUT
app.put('/users/:id', (req, res) => {
res.send(`تم تحديث المستخدم ${req.params.id}`);
});
// طلب DELETE
app.delete('/users/:id', (req, res) => {
res.send(`تم حذف المستخدم ${req.params.id}`);
});
في Express، يتم تمرير كائني request
و response
لمعالجات الطلبات.
يحتوي على معلومات عن الطلب HTTP، مثل:
req.params
- المعلمات المستخرجة من URLreq.query
- معلمات الاستعلام من URLreq.body
- بيانات الجسم من الطلبreq.headers
- رؤوس HTTPreq.cookies
- ملفات تعريف الارتباطيستخدم لإرسال الاستجابة HTTP إلى العميل، ويوفر طرقًا مثل:
res.send()
- إرسال استجابةres.json()
- إرسال استجابة JSONres.render()
- تقديم قالبres.redirect()
- إعادة توجيه الطلبres.status()
- تعيين رمز حالة HTTP// استخدام معلمات URL ومعلمات الاستعلام
app.get('/users/:id', (req, res) => {
const userId = req.params.id;
const sortBy = req.query.sortBy || 'name';
res.json({
userId: userId,
sortBy: sortBy,
message: `عرض المستخدم ${userId} مرتبًا حسب ${sortBy}`
});
});
// إرسال استجابات مختلفة
app.get('/examples/text', (req, res) => {
res.send('استجابة نصية بسيطة');
});
app.get('/examples/json', (req, res) => {
res.json({ message: 'استجابة JSON' });
});
app.get('/examples/status', (req, res) => {
res.status(201).send('تم إنشاء المورد');
});
app.get('/examples/redirect', (req, res) => {
res.redirect('/examples/json');
});
التوجيه في Express يشير إلى كيفية استجابة تطبيقك للطلب من العميل على مسار (URL) وطريقة HTTP معينة.
التوجيه الأساسي في Express يتم تعريفه باستخدام طرق تطبيق Express التي تتطابق مع طرق HTTP. يتكون كل طريق من مسار URL ودالة معالج واحدة أو أكثر.
// تعريف طريق بسيط
app.get('/hello', (req, res) => {
res.send('مرحباً بالعالم');
});
// طريق بمعلمات URL
app.get('/users/:userId', (req, res) => {
res.send(`معلومات المستخدم: ${req.params.userId}`);
});
تستخدم معلمات URL لالتقاط القيم المحددة في عنوان URL. يمكن الوصول إليها عبر req.params
.
// معلمة URL إلزامية
app.get('/products/:category/:id', (req, res) => {
const { category, id } = req.params;
res.send(`المنتج: ${id} في التصنيف: ${category}`);
});
// معلمة URL اختيارية
app.get('/products/:category?', (req, res) => {
const category = req.params.category || 'جميع المنتجات';
res.send(`عرض ${category}`);
});
يمكنك استخدام التعبيرات النمطية في المسارات للتحكم بشكل أدق في المطابقة:
// التحقق من أن المعلمة عبارة عن ملف PDF
app.get(/.*\.pdf$/, (req, res) => {
res.send('محتوى ملف PDF');
});
معلمات الاستعلام (Query Parameters) تأتي في نهاية URL بعد علامة الاستفهام (؟) ويمكن الوصول إليها عبر req.query
.
// استخدام معلمات الاستعلام للبحث
app.get('/search', (req, res) => {
const query = req.query.q;
const limit = req.query.limit || 10;
res.send(`نتائج البحث عن: ${query} (عرض ${limit} نتيجة)`);
});
يمكنك تنظيم المسارات في ملفات منفصلة باستخدام express.Router
، مما يساعد في الحفاظ على كود أكثر تنظيماً:
// في ملف routes/users.js
const express = require('express');
const router = express.Router();
// مسارات المستخدمين
router.get('/', (req, res) => {
res.send('قائمة المستخدمين');
});
router.get('/:id', (req, res) => {
res.send(`عرض المستخدم: ${req.params.id}`);
});
router.post('/', (req, res) => {
res.send('إنشاء مستخدم جديد');
});
// تصدير الموجه
module.exports = router;
// في ملف app.js
const usersRouter = require('./routes/users');
app.use('/users', usersRouter);
الوسائط (Middleware) هي دوال تحصل على الوصول إلى كائنات الطلب (req) والاستجابة (res) ودالة next في دورة طلب-استجابة التطبيق. يمكن للوسائط:
مثال على وسيط بسيط لتسجيل معلومات الطلب:
const loggerMiddleware = (req, res, next) => {
console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
next(); // استدعاء الوسيط التالي
};
// استخدام الوسيط على مستوى التطبيق
app.use(loggerMiddleware);
يمكن تطبيق الوسائط على مستويات مختلفة:
// يتم تطبيقه على جميع الطلبات
app.use(middlewareFunction);
// يتم تطبيقه فقط على الطلبات التي تستهدف مسار محدد
app.use('/api', middlewareFunction);
// يتم تطبيقه فقط على معالج محدد
app.get('/products', middlewareFunction, (req, res) => {
res.send('قائمة المنتجات');
});
لتحليل طلبات JSON القادمة وتوفيرها في req.body
:
app.use(express.json());
لتحليل بيانات النموذج (form data) وتوفيرها في req.body
:
app.use(express.urlencoded({ extended: true }));
لخدمة الملفات الثابتة مثل الصور وملفات CSS و JavaScript:
app.use(express.static('public'));
ترتيب تعريف الوسائط مهم! يتم استدعاء الوسائط بالترتيب الذي تم تعريفها به. لذلك، عادة ما يتم تعريف الوسائط العامة (مثل تسجيل الطلبات أو تحليل الجسم) قبل معالجات المسارات الخاصة.
تسمح محركات القوالب بإنشاء صفحات HTML ديناميكية عن طريق إدخال بيانات في قوالب. يدعم Express العديد من محركات القوالب مثل EJS و Pug و Handlebars والمزيد.
لاستخدام محرك قوالب، تحتاج إلى تثبيته وتكوينه في تطبيقك:
# تثبيت محرك قوالب EJS
npm install ejs
// تكوين Express لاستخدام EJS
app.set('view engine', 'ejs');
app.set('views', './views'); // مجلد لتخزين القوالب
مثال على قالب EJS بسيط (views/index.ejs):
<%= title %>
<%= heading %>
<% for(let i=0; i < items.length; i++) { %>
- <%= items[i] %>
<% } %>
استخدام الدالة res.render()
لتقديم القالب مع البيانات:
app.get('/', (req, res) => {
res.render('index', {
title: 'صفحة Express الرئيسية',
heading: 'مرحباً بالعالم!',
items: ['عنصر 1', 'عنصر 2', 'عنصر 3']
});
});
قم باستكشاف محركات قوالب مختلفة لاختيار الأنسب لمشروعك. تتميز Pug بصيغة مختصرة، بينما توفر EJS صيغة قريبة من HTML، وتستخدم Handlebars نهجًا حديثًا بالقوالب المستقلة عن المنطق.
يوفر Express آلية لخدمة الملفات الثابتة مثل الصور وملفات CSS و JavaScript عبر الوسيط express.static
.
لخدمة ملفات من مجلد public
:
app.use(express.static('public'));
بهذه الطريقة، يمكن الوصول إلى الملفات الموجودة في المجلد public
مباشرة من الجذر:
http://localhost:3000/images/logo.png
يخدم الملف public/images/logo.png
http://localhost:3000/css/style.css
يخدم الملف public/css/style.css
http://localhost:3000/js/app.js
يخدم الملف public/js/app.js
يمكنك أيضًا إضافة بادئة للمسار الافتراضي:
app.use('/static', express.static('public'));
الآن، يمكن الوصول إلى الملفات باستخدام البادئة:
http://localhost:3000/static/images/logo.png
http://localhost:3000/static/css/style.css
من المهم عدم وضع ملفات حساسة في المجلدات المخدمة بشكل ثابت، حيث يمكن للمستخدمين الوصول إلى جميع الملفات الموجودة في هذه المجلدات. حدد بعناية الملفات التي تريد مشاركتها عبر هذه الآلية.
تعتبر معالجة الأخطاء مهمة في تطبيقات Express لضمان تجربة مستخدم جيدة وتوفير سلوك متوقع في حالات الفشل.
في Express، يتم تعريف وسائط معالجة الأخطاء بأربعة معلمات (بدلاً من ثلاثة كالوسائط العادية):
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('حدث خطأ ما!');
});
لمعالجة المسارات غير الموجودة، أضف وسيطًا في نهاية سلسلة الوسائط:
// يجب أن يكون هذا الوسيط بعد جميع المسارات الأخرى
app.use((req, res, next) => {
res.status(404).send("عذراً، لم نتمكن من العثور على ما تبحث عنه!");
});
للتعامل مع الأخطاء في دوال غير متزامنة، يمكنك:
// استخدام try/catch مع async/await
app.get('/async-data', async (req, res, next) => {
try {
const data = await fetchData();
res.json(data);
} catch (error) {
next(error); // تمرير الخطأ إلى معالج الأخطاء
}
});
// أو استخدام وعود
app.get('/promise-data', (req, res, next) => {
fetchData()
.then(data => res.json(data))
.catch(next); // تمرير الخطأ مباشرة إلى next
});
يمكنك إنشاء بنية أكثر تعقيدًا لمعالجة الأخطاء المختلفة:
// تعريف أنواع أخطاء مخصصة
class NotFoundError extends Error {
constructor(message) {
super(message);
this.name = 'NotFoundError';
this.statusCode = 404;
}
}
class ValidationError extends Error {
constructor(message, validationErrors) {
super(message);
this.name = 'ValidationError';
this.statusCode = 400;
this.validationErrors = validationErrors;
}
}
// رمي أخطاء في معالجات المسارات
app.get('/users/:id', (req, res, next) => {
const user = findUser(req.params.id);
if (!user) {
return next(new NotFoundError('المستخدم غير موجود'));
}
res.json(user);
});
// معالج أخطاء شامل
app.use((err, req, res, next) => {
console.error(err);
const statusCode = err.statusCode || 500;
const errorResponse = {
error: {
message: err.message || 'حدث خطأ داخلي في الخادم',
type: err.name || 'Error'
}
};
// إضافة تفاصيل التحقق إذا كانت موجودة
if (err.validationErrors) {
errorResponse.error.validationErrors = err.validationErrors;
}
// في بيئة التطوير فقط، أضف معلومات التتبع
if (process.env.NODE_ENV === 'development') {
errorResponse.error.stack = err.stack;
}
res.status(statusCode).json(errorResponse);
});
يمكن استخدام Express مع مجموعة متنوعة من قواعد البيانات، سواء كانت قواعد بيانات SQL مثل MySQL و PostgreSQL و SQLite، أو قواعد بيانات NoSQL مثل MongoDB.
// تثبيت: npm install mongoose
const mongoose = require('mongoose');
// الاتصال بقاعدة البيانات
mongoose.connect('mongodb://localhost:27017/myapp', {
useNewUrlParser: true,
useUnifiedTopology: true
})
.then(() => console.log('تم الاتصال بقاعدة البيانات MongoDB بنجاح'))
.catch(err => console.error('خطأ في الاتصال بقاعدة البيانات:', err));
// تعريف مخطط البيانات (Schema)
const userSchema = new mongoose.Schema({
name: String,
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
createdAt: { type: Date, default: Date.now }
});
// إنشاء نموذج
const User = mongoose.model('User', userSchema);
// استخدام النموذج في المسارات
app.post('/users', async (req, res, next) => {
try {
const newUser = new User(req.body);
const savedUser = await newUser.save();
res.status(201).json(savedUser);
} catch (error) {
next(error);
}
});
// تثبيت: npm install mysql2
const mysql = require('mysql2/promise');
// إنشاء تجمع اتصالات
const pool = mysql.createPool({
host: 'localhost',
user: 'user',
password: 'password',
database: 'myapp',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
// استخدام تجمع الاتصالات في المسارات
app.get('/users', async (req, res, next) => {
try {
const [rows] = await pool.query('SELECT * FROM users');
res.json(rows);
} catch (error) {
next(error);
}
});
// تثبيت: npm install pg
const { Pool } = require('pg');
const pool = new Pool({
user: 'user',
host: 'localhost',
database: 'myapp',
password: 'password',
port: 5432,
});
app.get('/users/:id', async (req, res, next) => {
try {
const { rows } = await pool.query('SELECT * FROM users WHERE id = $1', [req.params.id]);
if (rows.length === 0) {
return res.status(404).json({ message: 'المستخدم غير موجود' });
}
res.json(rows[0]);
} catch (error) {
next(error);
}
});
// تثبيت: npm install better-sqlite3
const Database = require('better-sqlite3');
const db = new Database('myapp.db', { verbose: console.log });
// إنشاء جدول إذا لم يكن موجودًا
db.exec(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
email TEXT UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`);
app.post('/users', (req, res) => {
try {
const { name, email } = req.body;
const stmt = db.prepare('INSERT INTO users (name, email) VALUES (?, ?)');
const result = stmt.run(name, email);
res.status(201).json({
id: result.lastInsertRowid,
name,
email
});
} catch (error) {
res.status(400).json({ error: error.message });
}
});
لتبسيط التفاعل مع قواعد البيانات، يمكنك استخدام ORM (Object-Relational Mapping) أو أدوات بناء الاستعلامات:
تعد المصادقة والتفويض من المتطلبات الرئيسية للعديد من تطبيقات الويب. هناك عدة طرق لتنفيذ المصادقة في Express، ولكن أحد الخيارات الشائعة هو استخدام Passport.js.
Passport هي مكتبة مصادقة وسيطة لـ Node.js، تعمل بشكل جيد مع Express وتدعم العديد من استراتيجيات المصادقة.
// تثبيت: npm install passport passport-local express-session
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const session = require('express-session');
// إعداد express-session
app.use(session({
secret: 'your-secret-key',
resave: false,
saveUninitialized: false
}));
// تهيئة Passport
app.use(passport.initialize());
app.use(passport.session());
// إعداد استراتيجية المصادقة المحلية
passport.use(new LocalStrategy(
{ usernameField: 'email' },
async (email, password, done) => {
try {
// البحث عن المستخدم في قاعدة البيانات
const user = await User.findOne({ email: email });
if (!user) {
return done(null, false, { message: 'البريد الإلكتروني غير صحيح' });
}
// التحقق من كلمة المرور (باستخدام دالة مناسبة للتحقق من التشفير)
const isValid = await validatePassword(password, user.password);
if (!isValid) {
return done(null, false, { message: 'كلمة المرور غير صحيحة' });
}
return done(null, user);
} catch (error) {
return done(error);
}
}
));
// تسلسل/إلغاء تسلسل معلومات المستخدم من/إلى الجلسة
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser(async (id, done) => {
try {
const user = await User.findById(id);
done(null, user);
} catch (error) {
done(error);
}
});
// مسارات تسجيل الدخول/الخروج
app.post('/login', passport.authenticate('local', {
successRedirect: '/dashboard',
failureRedirect: '/login',
failureFlash: true // إذا كنت تستخدم connect-flash
}));
app.get('/logout', (req, res) => {
req.logout(function(err) {
if (err) { return next(err); }
res.redirect('/');
});
});
// وسيط للتحقق من المصادقة
const isAuthenticated = (req, res, next) => {
if (req.isAuthenticated()) {
return next();
}
res.redirect('/login');
};
// استخدام وسيط المصادقة لحماية المسارات
app.get('/dashboard', isAuthenticated, (req, res) => {
res.render('dashboard', { user: req.user });
});
يمكن استخدام JWT كبديل لجلسات المستخدم، وهو مفيد بشكل خاص لواجهات برمجة التطبيقات (APIs):
// تثبيت: npm install jsonwebtoken
const jwt = require('jsonwebtoken');
const secretKey = 'your-secret-key';
// مسار تسجيل الدخول وإنشاء JWT
app.post('/api/login', async (req, res) => {
const { email, password } = req.body;
try {
// التحقق من المستخدم وكلمة المرور
const user = await User.findOne({ email });
if (!user || !await validatePassword(password, user.password)) {
return res.status(401).json({ message: 'بيانات الاعتماد غير صالحة' });
}
// إنشاء توكن JWT
const token = jwt.sign(
{ id: user._id, email: user.email },
secretKey,
{ expiresIn: '1h' }
);
res.json({ token });
} catch (error) {
res.status(500).json({ message: 'خطأ في الخادم' });
}
});
// وسيط للتحقق من JWT
const authenticateJWT = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader) {
return res.status(401).json({ message: 'الوصول مرفوض. التوكن مطلوب.' });
}
const token = authHeader.split(' ')[1];
jwt.verify(token, secretKey, (err, user) => {
if (err) {
return res.status(403).json({ message: 'الوصول مرفوض. التوكن غير صالح.' });
}
req.user = user;
next();
});
};
// استخدام وسيط JWT لحماية المسارات
app.get('/api/protected', authenticateJWT, (req, res) => {
res.json({ message: 'هذه بيانات محمية', user: req.user });
});
Express مناسب جدًا لبناء واجهات برمجة التطبيقات (APIs). في هذا القسم، سنتناول أفضل الممارسات والتقنيات لتطوير واجهات API RESTful.
يجب أن تتبع واجهات API مبادئ REST الأساسية:
/users
)// مثال API RESTful لمورد المستخدمين
const router = express.Router();
// الحصول على قائمة المستخدمين
router.get('/users', async (req, res) => {
try {
const users = await User.find({}, '-password'); // استبعاد كلمة المرور
res.json(users);
} catch (error) {
res.status(500).json({ error: 'فشل في استرجاع المستخدمين' });
}
});
// الحصول على مستخدم واحد بواسطة المعرف
router.get('/users/:id', async (req, res) => {
try {
const user = await User.findById(req.params.id, '-password');
if (!user) {
return res.status(404).json({ error: 'المستخدم غير موجود' });
}
res.json(user);
} catch (error) {
res.status(500).json({ error: 'فشل في استرجاع المستخدم' });
}
});
// إنشاء مستخدم جديد
router.post('/users', async (req, res) => {
try {
const { name, email, password } = req.body;
// التحقق من وجود بيانات إلزامية
if (!name || !email || !password) {
return res.status(400).json({ error: 'جميع الحقول إلزامية' });
}
// التحقق من عدم وجود بريد إلكتروني مكرر
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(409).json({ error: 'البريد الإلكتروني موجود بالفعل' });
}
const newUser = new User({ name, email, password });
await newUser.save();
// إعادة المستخدم بدون كلمة المرور
const userResponse = newUser.toObject();
delete userResponse.password;
res.status(201).json(userResponse);
} catch (error) {
res.status(500).json({ error: 'فشل في إنشاء المستخدم' });
}
});
// تحديث مستخدم
router.put('/users/:id', async (req, res) => {
try {
const { name, email } = req.body;
const updatedUser = await User.findByIdAndUpdate(
req.params.id,
{ name, email },
{ new: true, runValidators: true, select: '-password' }
);
if (!updatedUser) {
return res.status(404).json({ error: 'المستخدم غير موجود' });
}
res.json(updatedUser);
} catch (error) {
res.status(500).json({ error: 'فشل في تحديث المستخدم' });
}
});
// حذف مستخدم
router.delete('/users/:id', async (req, res) => {
try {
const user = await User.findByIdAndDelete(req.params.id);
if (!user) {
return res.status(404).json({ error: 'المستخدم غير موجود' });
}
res.status(204).send(); // No Content
} catch (error) {
res.status(500).json({ error: 'فشل في حذف المستخدم' });
}
});
من المهم التحقق من بيانات الإدخال للـ API. يمكنك استخدام مكتبات مثل express-validator
لهذا الغرض:
// تثبيت: npm install express-validator
const { body, validationResult } = require('express-validator');
router.post('/users', [
// قواعد التحقق
body('name').trim().notEmpty().withMessage('الاسم مطلوب'),
body('email').isEmail().withMessage('بريد إلكتروني غير صالح')
.normalizeEmail().custom(async (email) => {
const user = await User.findOne({ email });
if (user) {
throw new Error('البريد الإلكتروني موجود بالفعل');
}
return true;
}),
body('password').isLength({ min: 6 }).withMessage('كلمة المرور يجب أن تكون 6 أحرف على الأقل')
], async (req, res) => {
// التحقق من وجود أخطاء
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// متابعة معالجة الطلب
try {
const user = new User(req.body);
await user.save();
res.status(201).json(user);
} catch (error) {
res.status(500).json({ error: 'فشل في إنشاء المستخدم' });
}
});
لتحسين أداء واجهة API، يمكنك استخدام التخزين المؤقت (Caching):
// تثبيت: npm install memory-cache
const cache = require('memory-cache');
// وسيط التخزين المؤقت
const cacheMiddleware = (duration) => {
return (req, res, next) => {
const key = '__express__' + req.originalUrl || req.url;
const cachedBody = cache.get(key);
if (cachedBody) {
res.send(cachedBody);
return;
} else {
res.sendResponse = res.send;
res.send = (body) => {
cache.put(key, body, duration * 1000);
res.sendResponse(body);
};
next();
}
};
};
// استخدام التخزين المؤقت لمدة 10 دقائق
app.get('/api/products', cacheMiddleware(600), async (req, res) => {
const products = await Product.find();
res.json(products);
});
توثيق واجهة API مهم جدًا. يمكنك استخدام أدوات مثل Swagger/OpenAPI:
// تثبيت: npm install swagger-jsdoc swagger-ui-express
const swaggerJsDoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');
const swaggerOptions = {
swaggerDefinition: {
openapi: '3.0.0',
info: {
title: 'API الخاصة بتطبيق Express',
version: '1.0.0',
description: 'واجهة برمجة التطبيقات RESTful مع Express'
},
servers: [
{
url: 'http://localhost:3000'
}
]
},
apis: ['./routes/*.js'] // ملفات الطرق التي تحتوي على توثيق
};
const swaggerDocs = swaggerJsDoc(swaggerOptions);
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocs));
// مثال على توثيق Swagger في ملف الطرق
/**
* @swagger
* /api/users:
* get:
* summary: استرجاع قائمة المستخدمين
* description: استرجاع قائمة بجميع المستخدمين من قاعدة البيانات
* responses:
* 200:
* description: قائمة المستخدمين
* content:
* application/json:
* schema:
* type: array
* items:
* type: object
* properties:
* id:
* type: string
* name:
* type: string
* email:
* type: string
*/
عند تحضير تطبيق Express للإنتاج، هناك عدة اعتبارات مهمة يجب مراعاتها.
استخدم متغيرات البيئة لتخزين البيانات الحساسة وإعدادات البيئة المختلفة:
// تثبيت: npm install dotenv
// في ملف app.js
require('dotenv').config();
const app = express();
const PORT = process.env.PORT || 3000;
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/myapp';
mongoose.connect(MONGODB_URI)
.then(() => console.log('تم الاتصال بقاعدة البيانات'))
.catch(err => console.error('خطأ في الاتصال بقاعدة البيانات:', err));
app.listen(PORT, () => {
console.log(`الخادم يعمل على المنفذ ${PORT}`);
});
ملف .env
:
PORT=3000
MONGODB_URI=mongodb://username:password@host:port/database
JWT_SECRET=your-secret-key
NODE_ENV=production
PM2 هو مدير عمليات إنتاج لتطبيقات Node.js يساعد في الحفاظ على التطبيق قيد التشغيل:
# تثبيت PM2 عالمياً
npm install -g pm2
# بدء تشغيل التطبيق
pm2 start app.js --name "myapp"
# عرض حالة التطبيقات
pm2 status
# إعادة تشغيل التطبيق
pm2 restart myapp
# إيقاف التطبيق
pm2 stop myapp
# تكوين PM2 لإعادة تشغيل التطبيق تلقائياً عند إعادة تشغيل الخادم
pm2 startup
pm2 save
تطبيق ممارسات الأمان الأساسية باستخدام helmet
:
// تثبيت: npm install helmet
const helmet = require('helmet');
app.use(helmet()); // يضبط رؤوس HTTP المتعلقة بالأمان
استخدم compression لتقليل حجم الاستجابات:
// تثبيت: npm install compression
const compression = require('compression');
app.use(compression());
استخدم مكتبة لتسجيل الأحداث مثل winston
لمراقبة التطبيق:
// تثبيت: npm install winston
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple()
}));
}
// استخدام السجل
logger.info('تم بدء تشغيل الخادم');
logger.error('حدث خطأ', { error: err });
يمكن نشر تطبيقات Express على مختلف منصات الاستضافة:
استخدام أفضل الممارسات في Express يمكن أن يساعد في تطوير تطبيقات أكثر استقرارًا وقابلية للصيانة والتوسع.
تنظيم المشروع بطريقة منطقية تسهل الصيانة:
my-express-app/
├── config/ # إعدادات التطبيق
│ ├── db.js # تكوين قاعدة البيانات
│ └── passport.js # تكوين المصادقة
├── controllers/ # منطق معالجة الطلبات
│ ├── userController.js
│ └── productController.js
├── middlewares/ # وسائط مخصصة
│ ├── auth.js
│ └── errorHandler.js
├── models/ # نماذج البيانات
│ ├── User.js
│ └── Product.js
├── routes/ # تعريفات المسارات
│ ├── userRoutes.js
│ └── productRoutes.js
├── utils/ # وظائف مساعدة
│ ├── logger.js
│ └── validators.js
├── public/ # ملفات ثابتة
├── views/ # قوالب العرض
├── app.js # نقطة الدخول للتطبيق
├── package.json
└── README.md
التأكد من معالجة جميع الأخطاء المحتملة بشكل مناسب:
تطبيق إجراءات الأمان الأساسية:
// تثبيت: npm install bcrypt cors express-rate-limit
const bcrypt = require('bcrypt');
const cors = require('cors');
const rateLimit = require('express-rate-limit');
// تخزين كلمات المرور بشكل آمن
const hashPassword = async (password) => {
const salt = await bcrypt.genSalt(10);
return await bcrypt.hash(password, salt);
};
// تكوين CORS
app.use(cors({
origin: 'https://yourdomain.com', // السماح فقط بالطلبات من هذا المصدر
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
// تقييد معدل الطلبات
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 دقيقة
max: 100, // الحد الأقصى للطلبات لكل IP
message: 'تم تجاوز الحد الأقصى للطلبات، يرجى المحاولة لاحقاً'
});
app.use('/api/', apiLimiter);
تحسين أداء التطبيق:
تنفيذ اختبارات شاملة للتطبيق:
// تثبيت: npm install jest supertest
// tests/userRoutes.test.js
const request = require('supertest');
const app = require('../app');
const User = require('../models/User');
beforeEach(async () => {
await User.deleteMany({});
});
describe('مسارات المستخدم', () => {
it('يجب إنشاء مستخدم جديد', async () => {
const response = await request(app)
.post('/api/users')
.send({
name: 'مستخدم اختبار',
email: '[email protected]',
password: 'password123'
});
expect(response.statusCode).toBe(201);
expect(response.body).toHaveProperty('_id');
expect(response.body.name).toBe('مستخدم اختبار');
});
it('يجب استرجاع قائمة المستخدمين', async () => {
// إنشاء مستخدم اختبار
await User.create({
name: 'مستخدم اختبار',
email: '[email protected]',
password: 'password123'
});
const response = await request(app).get('/api/users');
expect(response.statusCode).toBe(200);
expect(response.body.length).toBe(1);
expect(response.body[0].name).toBe('مستخدم اختبار');
});
});