Node.js: Express متقدم
آخر تحديث: 2026-08-26
1. أزمة الإنتاج التي واجهها بوب
واجهت واجهة برمجة التطبيقات (API) الخاصة بـ«بوب» مشاكل في أول يوم لها: فقد أرسل أحد المستخدمين عنوان بريد إلكتروني يحتوي على أحرف مشوشة، مما تسبب في حدوث خطأ في قاعدة البيانات أدى إلى تعطل الموقع بالكامل؛ وقام شخص آخر بتحميل صورة بحجم 500 ميغابايت، مما أدى إلى امتلاء مساحة القرص على الفور؛ كما كتب أحد المنافسين برنامجًا نصيًّا أرسل 1,000 طلب في الثانية، مما تسبب في إرجاع الخادم لخطأ 502.
يكفي وجود
app.use(errorHandler)واحد فقط لمنع 80% من حوادث الإنتاج.
Bob Mine Clearance Timeline:
Day 1 📧 Invalid email address → Database Error → 500 Error
Day 2 🖼️ 500MB Upload → Disk Full → Service Outage
Day 3 🤖 1000 req/s → CPU 100% → 502 Bad Gateway
Day 4 🔒 Add middleware → Take Them One by One → Stable service
2. البرمجيات الوسيطة لمعالجة الأخطاء
(1) نظام التوقيع ذو المعلمات الأربعة
Express identifies error middleware by the number of parameters—there must be 4 parameters (err, req, res, next); if one is missing, it becomes a regular middleware.
▶ مثال: برمجيات وسيطة عالمية لمعالجة الأخطاء
const express = require('express');
const app = express();
app.get('/boom', (req, res, next) => {
try {
throw new Error('Blown up on purpose');
} catch (err) {
next(err);
}
});
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(err.status || 500).json({
code: err.status || 500,
data: null,
message: err.message
});
});
app.listen(3000);
(2) فئات الأخطاء التجارية المخصصة
▶ مثال: التمييز بين الأخطاء التشغيلية وأخطاء HTTP
class AppError extends Error {
constructor(message, status) {
super(message);
this.status = status;
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
}
class NotFoundError extends AppError {
constructor(resource) {
super(`${resource} Not found`, 404);
}
}
class ValidationError extends AppError {
constructor(message) {
super(message, 400);
}
}
app.get('/users/:id', (req, res, next) => {
const user = findUser(req.params.id);
if (!user) return next(new NotFoundError('User'));
res.json({ code: 0, data: user, message: 'ok' });
});
(3) مقارنة بين طرق معالجة الأخطاء
| الطريقة | عدد المعلمات | النطاق | حالات الاستخدام | دعم التشغيل غير المتزامن |
|---|---|---|---|---|
app.use(errHandler) |
4 | جميع الأخطاء العامة | معالجة شاملة نهائية | يتطلب تدخلًا يدويًّا next(err) |
try/catch + next(err) |
— | مسار واحد | رمز المزامنة | المزامنة فقط |
express-async-errors |
0 | خطأ عالمي في المعالجة غير المتزامنة | توجيه async/await | تلقائي |
وحدة domain (مهملة) |
— | على مستوى العملية | غير موصى بها | — |
process.on('uncaughtException') |
— | على مستوى العملية | خط الدفاع الأخير | عالمي |
وعد .catch() |
— | وعد واحد | عملية غير متزامنة واحدة | واحد |
3. express-validator Request Validation
(1) كيفية استخدام سلسلة التحقق والبرمجيات الوسيطة
يعتمد express-validator على مكتبة validator.js. ويستخدم «سلسلة التحقق من الصحة» لتعريف القواعد بطريقة إعلانية، كما يقوم تلقائيًا بتجميع الأخطاء عند فشل عملية التحقق من الصحة.
▶ مثال: التحقق من تسجيل المستخدم
const { body, validationResult } = require('express-validator');
app.post('/register',
body('username')
.isLength({ min: 3, max: 20 }).withMessage('The username must3-20Character')
.isAlphanumeric().withMessage('Usernames must consist of alphanumeric characters only.'),
body('email')
.isEmail().withMessage('Invalid email address')
.normalizeEmail(),
body('password')
.isLength({ min: 8 }).withMessage('Password must be at least 8 characters')
.matches(/\d/).withMessage('Passwords must contain numbers')
.matches(/[A-Z]/).withMessage('The password must contain uppercase letters.'),
body('age')
.optional()
.isInt({ min: 1, max: 150 }).withMessage('Age requirement1-150'),
(req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
code: 400,
data: errors.array(),
message: 'Request verification failed'
});
}
next();
},
(req, res) => {
res.json({ code: 0, data: req.body, message: 'ok' });
}
);
(2) أدوات التحقق الشائعة في express-validator
| أداة التحقق | الغرض | مثال | المُعدِّلات المرتبطة |
|---|---|---|---|
isEmail() |
البريد الإلكتروني | body('email').isEmail() |
.normalizeEmail() |
isLength() |
الطول | body('name').isLength({min:2,max:50}) |
— |
isInt() / isFloat() |
الرقم | query('page').isInt({min:1}) |
.toInt() |
isBoolean() |
منطقية | body('active').isBoolean() |
.toBoolean() |
isDate() |
التاريخ | body('birthday').isDate() |
.toDate() |
isURL() |
رابط | body('website').isURL() |
— |
isIn() |
التعداد | body('role').isIn(['admin','user']) |
— |
matches() |
التعبير النمطي | body('code').matches(/^\d{6}$/) |
— |
isMongoId() |
ObjectId | param('id').isMongoId() |
— |
optional() |
اختياري | body('nickname').optional().isLength({max:30}) |
{nullable:true} |
(3) مقارنة بين «express-validator» و«Joi»
| معايير المقارنة | express-validator | Joi |
|---|---|---|
| طريقة التكامل | برمجيات وسيطة Native Express | مكتبة تحقق مستقلة؛ تتطلب تكاملاً يدويًّا |
| أسلوب الصياغة | الاستدعاءات المتسلسلة، التعريف حقلًا حقلًا | كائن المخطط، التعريف لمرة واحدة |
| التبعيات الأساسية | validator.js | التنفيذ المخصص |
| مجموعة الأخطاء | validationResult() التجميع التلقائي |
validate().error المعالجة اليدوية |
| حالات الاستخدام | التكامل السريع مع مشاريع Express | أي مشروع Node.js |
| منحنى التعلم | منخفض (إذا كنت على دراية بـ validator.js) | متوسط (بناء جملة فريد في Schema) |
| تحويل النوع | .toInt() .toDate()، إلخ. |
التحويل التلقائي للنوع |
| حجم المجتمع | عدد التنزيلات الأسبوعية ~1.5 مليون | عدد التنزيلات الأسبوعية ~5 ملايين |
4. تحميل الملفات باستخدام multer
(1) ثلاث استراتيجيات للتخزين
يوفر multer ثلاثة أوضاع للتحميل: single وarray وfields. وتشمل خيارات التخزين memoryStorage (الذاكرة) وdiskStorage (القرص).
▶ مثال: التخزين على القرص + تصفية الملفات
const multer = require('multer');
const path = require('path');
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'uploads/');
},
filename: (req, file, cb) => {
const ext = path.extname(file.originalname);
const uniqueName = `${Date.now()}-${Math.round(Math.random() * 1e9)}${ext}`;
cb(null, uniqueName);
}
});
const fileFilter = (req, file, cb) => {
const allowed = /\.(jpg|jpeg|png|gif|webp)$/i;
if (allowed.test(path.extname(file.originalname))) {
cb(null, true);
} else {
cb(new Error('Supports only jpg/png/gif/webp Format'), false);
}
};
const upload = multer({
storage,
fileFilter,
limits: { fileSize: 5 * 1024 * 1024 }
});
app.post('/avatar', upload.single('avatar'), (req, res) => {
if (!req.file) return res.status(400).json({ code: 400, data: null, message: 'Please upload the file' });
res.json({
code: 0,
data: { url: `/uploads/${req.file.filename}`, size: req.file.size },
message: 'ok'
});
});
app.post('/photos', upload.array('photos', 9), (req, res) => {
const urls = req.files.map(f => `/uploads/${f.filename}`);
res.json({ code: 0, data: urls, message: 'ok' });
});
(2) مقارنة بين خيارات تكوين مولتر
| عنصر التكوين | النوع | القيمة الافتراضية | الوصف | مثال |
|---|---|---|---|---|
storage |
محرك التخزين | memoryStorage |
محرك التخزين | multer.diskStorage({...}) |
dest |
سلسلة | — | الدليل المستهدف (إما هذا أو "storage") | 'uploads/' |
fileFilter |
الوظيفة | السماح بكل شيء | استدعاء رد تصفية الملفات | (req,file,cb)=>{...} |
limits.fileSize |
العدد | بلا حدود | الحد الأقصى لعدد البايتات لكل ملف | 5*1024*1024 |
limits.files |
العدد | غير محدود | الحد الأقصى لعدد الملفات المسموح بتحميلها | 9 |
limits.fields |
العدد | غير محدود | الحد الأقصى لعدد الحقول غير المتعلقة بالملفات | 10 |
limits.fieldSize |
رقم | 1 ميغابايت | الحد الأقصى لعدد البايتات للحقول غير المتعلقة بالملفات | 1024*100 |
limits.parts |
العدد | غير محدود | العدد الإجمالي للأجزاء المتعددة | 20 |
(3) التخزين في الذاكرة مقابل التخزين على القرص
| البعد | سعة الذاكرة | سعة القرص |
|---|---|---|
| موقع التخزين | الذاكرة (المخزن المؤقت) | ملف القرص |
req.file العقار |
buffer |
path، filename |
| حالات الاستخدام | الملفات الصغيرة، المعالجة في الوقت الفعلي (مثل ضغط الصور وحفظها) | الملفات الكبيرة، التخزين الدائم |
| الأداء | سريع (لا يتطلب عمليات إدخال/إخراج على القرص) | أبطأ قليلاً (يتطلب الكتابة على القرص) |
| مخاطر الذاكرة | نفاد الذاكرة (OOM) عند معالجة الملفات الكبيرة أو في ظل التزامن العالي | لا شيء |
| إعادة تشغيل «Lost» | نعم | لا |
| التحكم في اسم الملف | غير مطلوب | يتطلب استدعاءً مخصصًا filename |
5. تكوين خدمة الملفات الثابتة
(1) تفاصيل express.static
▶ مثال: خدمة ثابتة مع دلائل متعددة + التحكم في ذاكرة التخزين المؤقت
const express = require('express');
const app = express();
app.use('/static', express.static('public', {
maxAge: '7d',
etag: true,
lastModified: true,
immutable: true,
setHeaders: (res, filePath) => {
if (filePath.endsWith('.html')) {
res.setHeader('Cache-Control', 'no-cache');
}
if (filePath.match(/\.(jpg|png|gif|webp|svg)$/)) {
res.setHeader('Cache-Control', 'public, max-age=2592000, immutable');
}
}
}));
app.use('/uploads', express.static('uploads', {
maxAge: '30d',
dotfiles: 'deny'
}));
(2) الاعتبارات الأمنية للخدمات الثابتة
dotfilesاضبطه على'deny'لمنع تسرب الملفات الحساسة مثل.env- احرص على فصل مجلد التحميل عن مجلد الكود لمنع تنفيذ ملف
.js - استخدام Nginx/CDN لاستضافة الملفات الثابتة في بيئة الإنتاج؛ ويُستخدم Express حصريًّا لواجهات برمجة التطبيقات (APIs)
- تعيين قيمة معقولة لـ
Cache-Controlلتقليل استخدام النطاق الترددي
6. نموذج الرد الموحد
(1) مواصفات {code, data, message}
▶ مثال: برنامج وسيط لتغليف الاستجابات
const responseHandler = (req, res, next) => {
res.success = (data = null, message = 'ok') => {
res.json({ code: 0, data, message });
};
res.fail = (message = 'Operation Failed', code = -1, data = null) => {
res.json({ code, data, message });
};
res.paginate = (list, total, page, pageSize) => {
res.json({
code: 0,
data: { list, total, page, pageSize, totalPages: Math.ceil(total / pageSize) },
message: 'ok'
});
};
next();
};
app.use(responseHandler);
app.get('/users', (req, res) => {
const users = getUserList();
res.success(users);
});
app.get('/users/:id', (req, res, next) => {
const user = findUser(req.params.id);
if (!user) return res.fail('User does not exist', 404);
res.success(user);
});
(2) القوانين الستة الحديدية للتوسع الدولي
- ردًا على
message، لا تقم بكتابة النص الصيني بشكل ثابت في الكود؛ استخدم مفاتيح i18n مثل"error.user_not_found" - يقوم الخادم بتبديل اللغة بناءً على رأس
Accept-Languageأو المعلمة?lang=zh - رمز الخطأ
codeلا يعتمد على اللغة؛ حيث تقوم الواجهة الأمامية باسترداد النص المُترجم بناءً على هذا الرمز. - يتم عرض التواريخ والأوقات دائمًا بتنسيق ISO 8601 (
2025-01-15T08:30:00Z)؛ وتقوم الواجهة الأمامية بتنسيقها وفقًا للإعدادات الإقليمية. - الأرقام والعملات غير مهيأة مسبقًا؛ حيث يتم إرجاع القيمة الأصلية مع رمز العملة، وتقوم الواجهة الأمامية بعرضها وفقًا للإعدادات الإقليمية.
- تأكد من أن الحقل
msgفي مصفوفة الأخطاء يخضع أيضًا لعملية الترجمة الدولية (i18n) ولا يعرض مباشرةً رسالة باللغة الصينية
7. تحديد الحد الأقصى لمعدل الاستخدام
(1) تكوين «express-rate-limit»
▶ مثال: استراتيجية تحديد الحد الأقصى لمعدل الطلبات على مستويات متعددة
const rateLimit = require('express-rate-limit');
const globalLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
standardHeaders: true,
legacyHeaders: false,
message: { code: 429, data: null, message: 'Too many requests,Please try again later.' }
});
const apiLimiter = rateLimit({
windowMs: 60 * 1000,
max: 30,
message: { code: 429, data: null, message: 'API Exceeded the call limit' }
});
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
skipSuccessfulRequests: true,
message: { code: 429, data: null, message: 'Too many failed login attempts, please try again in 15 minutes' }
});
app.use(globalLimiter);
app.use('/api/', apiLimiter);
app.use('/auth/login', loginLimiter);
(2) قائمة برامج الوسيطة الأمنية
| البرامج الوسيطة | الغرض | السلوك الافتراضي | إعدادات التكوين الرئيسية | حزمة npm |
|---|---|---|---|---|
helmet |
رؤوس أمان HTTP | تعيين 15 رأس أمان | contentSecurityPolicy، hsts |
helmet |
express-rate-limit |
تحديد معدل الاستخدام | — | windowMs، max |
express-rate-limit |
cors |
التحكم عبر المجالات | رفض جميع الطلبات عبر المجالات | origin، methods، credentials |
cors |
express-validator |
التحقق من صحة المدخلات | — | body()، query()، param() |
express-validator |
multer |
تحميل الملف | — | limits، fileFilter |
multer |
express-mongo-sanitize |
حقن NoSQL | إزالة $ و. |
— | express-mongo-sanitize |
xss-clean |
تنقية XSS | الترميز الهروبي لـ HTML | — | xss-clean |
hpp |
تلوث المعلمات | أخذ القيمة الأخيرة | whitelist |
hpp |
compression ضغط gzip — ضغط threshold، level |
8. تهيئة البيئة: dotenv
(1) الاستخدام الأساسي لـ dotenv
▶ مثال: التحميل الهرمي لمتغيرات البيئة
const dotenv = require('dotenv');
const path = require('path');
dotenv.config({ path: path.resolve(process.env.NODE_ENV ? `.env.${process.env.NODE_ENV}` : '.env') });
const config = {
port: parseInt(process.env.PORT, 10) || 3000,
env: process.env.NODE_ENV || 'development',
db: {
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT, 10) || 27017,
name: process.env.DB_NAME || 'myapp_dev'
},
jwt: {
secret: process.env.JWT_SECRET,
expiresIn: process.env.JWT_EXPIRES_IN || '7d'
},
upload: {
maxFileSize: parseInt(process.env.MAX_FILE_SIZE, 10) || 5 * 1024 * 1024,
allowedTypes: (process.env.ALLOWED_TYPES || 'jpg,jpeg,png,gif,webp').split(',')
},
rateLimit: {
windowMs: parseInt(process.env.RATE_WINDOW_MS, 10) || 15 * 60 * 1000,
max: parseInt(process.env.RATE_MAX, 10) || 100
}
};
module.exports = config;
▶ مثال:(2) أفضل الممارسات المتعلقة بملفات .env
# .env ← Default(Development),Do not submit to Git
# .env.production ← Production Environment,Strictly Restrict Access
# .env.test ← Test Environment
PORT=3000
NODE_ENV=development
DB_HOST=localhost
DB_PORT=27017
DB_NAME=myapp_dev
JWT_SECRET=your-super-secret-key-change-in-production
JWT_EXPIRES_IN=7d
MAX_FILE_SIZE=5242880
ALLOWED_TYPES=jpg,jpeg,png,gif,webp
RATE_WINDOW_MS=900000
RATE_MAX=100
# .gitignore Must include
.env
.env.*
!.env.example
9. العملية الكاملة لمعالجة الطلبات السريعة
▶ مثال:(1) مخطط تدفق حورية البحر
flowchart TD
A[Client Request] --> B[helmet Safety Head]
B --> C[cors Cross-domain validation]
C --> D[rate-limit Traffic Flow Inspection]
D -->|429| E[Return Rate-Limiting Response]
D -->|Through| F[express.json Analysis Body]
F --> G[multer File Upload Processing]
G -->|The file is too large/Format error| H[next error]
G -->|Through| I[express-validator Verification]
I -->|Verification Failed| J[Back 400 Validation Error]
I -->|Through| K[Business Routing Processing]
K -->|Business Error| L[next error]
K -->|Success| M[Unified Response Encapsulation success/fail]
M --> N[Back JSON Response]
L --> O[Global Error Handling Middleware]
H --> O
O --> P[Standardized Error Responses code/data/message]
P --> N
style E fill:#f66,stroke:#333,color:#fff
style J fill:#f66,stroke:#333,color:#fff
style P fill:#f66,stroke:#333,color:#fff
style N fill:#6f6,stroke:#333
(2) مبادئ ترتيب تحميل البرامج الوسيطة
- ضع الإجراءات الأمنية (الخوذة، الحزام، الحد الأقصى للسرعة) في البداية تمامًا
- تلي ذلك فئات تحليل الطلبات (json، urlencoded، cookie)
- معالجة الملفات (multer) بعد التحليل
- فئة التحقق من الصحة (express-validator) قبل منطق الأعمال
- يتم توجيه الأعمال بشكل مركزي
- تسجيل غلاف الاستجابة قبل مسار الأعمال
- ضع دائمًا معالجة الأخطاء في النهاية
10. مثال شامل: إكمال تكوين أمان واجهة برمجة التطبيقات (Express API)
▶ مثال: خادم Express مخصص للإنتاج
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const rateLimit = require('express-rate-limit');
const multer = require('multer');
const { body, param, validationResult } = require('express-validator');
const compression = require('compression');
const path = require('path');
const config = require('./config');
const app = express();
app.use(helmet());
app.use(cors({ origin: config.corsOrigin, credentials: true }));
app.use(compression({ threshold: 1024 }));
app.use(express.json({ limit: '10kb' }));
app.use(express.urlencoded({ extended: true }));
const globalLimiter = rateLimit({
windowMs: config.rateLimit.windowMs,
max: config.rateLimit.max,
standardHeaders: true,
legacyHeaders: false,
message: { code: 429, data: null, message: 'error.rate_limited' }
});
app.use(globalLimiter);
const upload = multer({
storage: multer.diskStorage({
destination: 'uploads/',
filename: (req, file, cb) => {
const ext = path.extname(file.originalname);
cb(null, `${Date.now()}-${Math.random().toString(36).slice(2)}${ext}`);
}
}),
fileFilter: (req, file, cb) => {
const ext = path.extname(file.originalname).toLowerCase();
if (config.upload.allowedTypes.some(t => `.${t}` === ext)) {
cb(null, true);
} else {
cb(new Error('error.invalid_file_type'), false);
}
},
limits: { fileSize: config.upload.maxFileSize, files: 5 }
});
const responseHandler = (req, res, next) => {
res.success = (data = null, message = 'ok') => {
res.json({ code: 0, data, message });
};
res.fail = (message = 'error.internal', code = -1, data = null) => {
res.json({ code, data, message });
};
next();
};
app.use(responseHandler);
const validate = (rules) => [
...rules,
(req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
code: 400,
data: errors.array().map(e => ({ field: e.path, message: e.msg })),
message: 'error.validation_failed'
});
}
next();
}
];
app.post('/api/users',
validate([
body('username').isLength({ min: 3, max: 20 }).withMessage('error.username_length'),
body('email').isEmail().withMessage('error.invalid_email').normalizeEmail(),
body('password').isLength({ min: 8 }).withMessage('error.password_length')
]),
(req, res) => {
const user = createUser(req.body);
res.success(user, 'ok');
}
);
app.post('/api/upload',
upload.array('files', 5),
(req, res) => {
if (!req.files || req.files.length === 0) {
return res.fail('error.no_file_uploaded', 400);
}
const urls = req.files.map(f => `/uploads/${f.filename}`);
res.success(urls, 'ok');
}
);
app.get('/api/users/:id',
validate([param('id').isMongoId().withMessage('error.invalid_id')]),
(req, res) => {
const user = findUser(req.params.id);
if (!user) return res.fail('error.user_not_found', 404);
res.success(user);
}
);
app.use('/uploads', express.static('uploads', { maxAge: '30d', dotfiles: 'deny' }));
app.use((req, res) => {
res.status(404).json({ code: 404, data: null, message: 'error.not_found' });
});
class AppError extends Error {
constructor(message, status) {
super(message);
this.status = status;
this.isOperational = true;
}
}
app.use((err, req, res, next) => {
if (err instanceof multer.MulterError) {
if (err.code === 'LIMIT_FILE_SIZE') {
return res.status(413).json({ code: 413, data: null, message: 'error.file_too_large' });
}
return res.status(400).json({ code: 400, data: null, message: 'error.upload_failed' });
}
const status = err.status || 500;
const message = err.isOperational ? err.message : 'error.internal';
if (config.env === 'development') console.error(err.stack);
res.status(status).json({ code: status, data: null, message });
});
app.listen(config.port, () => {
console.log(`Server running on port ${config.port} [${config.env}]`);
});
11. ملخص هذا الدرس
- التوقيع ذو المعلمات الأربعة
(err, req, res, next)الخاص ببرنامج الوسيط الخاص بالأخطاء هو المفتاح الذي يعتمد عليه Express في عملية التعرف - يستخدم express-validator سلسلة التحقق من الصحة لتحديد القواعد، ويقوم
validationResult()بتجميع الأخطاء - يوفر
limits+fileFilterمن multer حماية مزدوجة ضد امتلاء القرص - الاستجابات الموحدة
{code, data, message}ضمان معالجة متسقة ويمكن التنبؤ بها في الواجهة الأمامية - تحديد الحد الأقصى لعدد الطلبات: تحديد الحد الأقصى لعدد الطلبات حسب المستويات: عام (متساهل)، للمستخدمين المسجلين (صارم)، واجهة برمجة التطبيقات (متوسط)
- استخدم dotenv للتحميل الهرمي
.env.{NODE_ENV}؛ لا تقم أبدًا بإدراج.envالملفات في Git - ترتيب تحميل البرامج الوسيطة: الأمان → التحليل → الملفات → التحقق من الصحة → منطق الأعمال → معالجة الأخطاء
❓ أسئلة شائعة
app.use قبل البرامج الوسيطة الخاصة بالمسارات، أما البرامج الوسيطة الموجودة داخل المسارات فتُنفَّذ بالترتيب المحدد في المسارات.express-rate-limit، وقم بتعيين النافذة الزمنية windowMs وعدد الطلبات max، وقم بإرجاع رمز الحالة 429 عند تجاوز الحد الأقصى.maxAge التي يوفرها express.static.- س: لماذا يجب أن تحتوي الوسيطة الخاصة بمعالجة الأخطاء على 4 معلمات بالضبط؟ ج: يستخدم Express
fn.lengthللتحقق من عدد معلمات الدالة؛ ولا يتم التعرف عليها كوسيطة لمعالجة الأخطاء إلا إذا كانت تحتوي على 4 معلمات. وإلا، فسيتم التعامل معها كوسيطة عادية ولن تتلقى الكائنerr. - س: ما الفرق الرئيسي بين express-validator و Joi؟ ج: express-validator هي مكتبة على غرار برامج الوسيطة (middleware) في Express تتيح إجراء التحقق من الصحة بشكل متسلسل، حقلًا تلو الآخر، وهي متكاملة بشكل عميق مع المسارات؛ أما Joi فهي مكتبة مستقلة للتحقق من صحة المخططات (schema) تحدد هياكل بيانات كاملة، وتتطلب الاستدعاء اليدوي لـ
validate()ومعالجة النتائج. - س: كيف أختار بين memoryStorage و diskStorage؟ ج: استخدم memoryStorage للملفات الصغيرة (<1 ميغابايت) التي تتطلب معالجة فورية (مثل إنشاء الصور المصغرة ثم حفظها في التخزين السحابي)؛ واستخدم diskStorage للملفات الكبيرة أو تلك التي يجب حفظها بشكل دائم لتجنب تجاوز سعة الذاكرة.
- س: كيف يمكنني تحديد حجم الملفات التي يتم تحميلها؟ ج: هناك ثلاث طبقات من الحماية: يقوم multer
limits.fileSizeبالتدخل على مستوى طبقة التطبيق، وexpress.json({limit:'10kb'})بالتدخل في نص الطلب، وNginxclient_max_body_sizeبالتدخل على مستوى طبقة البوابة. - س: ما هو express-async-errors؟ ج: حزمة لا تتطلب سوى
require('express-async-errors')سطر واحد من التعليمات البرمجية لالتقاط حالات رفض الـ Promise غير المعالجة تلقائيًّا في المسارات غير المتزامنة وإعادة توجيهها إلى البرمجيات الوسيطة الخاصة بالأخطاء، مما يلغي الحاجة إلى كتابة كتل try/catch لكل مسار. - س: في الردود الموحدة، هل يجب أن يستخدم «الرمز» الرقم 0 للإشارة إلى النجاح، أم يجب أن يستخدم رمز حالة HTTP؟ ج: يُوصى بأن تستخدم الرموز الخاصة بالأعمال
0للإشارة إلى النجاح (بصورة منفصلة عن رموز حالة HTTP)، بينما يجب الاستمرار في إرجاع رموز حالة HTTP وفقًا لمواصفات REST (200/400/404/500). وينبغي ترميز الأخطاء التجارية باستخدام أرقام سالبة أو أرقام موجبة محددة.
📖 ملخص
- 1 المفاهيم الأساسية وتطبيقات أزمة الإنتاج التي واجهها بوب
- 2 المفاهيم الأساسية واستخدامات البرمجيات الوسيطة لمعالجة الأخطاء
- 3 مفاهيم أساسية واستخدامات express-validator للتحقق من صحة الطلبات
- 4 مفاهيم أساسية واستخدامات multer في تحميل الملفات
- 5 مفاهيم أساسية واستخدامات التكوين الثابت لخدمة الملفات
- 6 مفاهيم أساسية واستخدامات تنسيق الاستجابة الموحد
- 7 مفاهيم أساسية واستخدامات تحديد معدل الاستخدام
- 8 مفاهيم أساسية واستخدامات dotenv لتكوين البيئة
📝 تمارين
- إضافة برنامج وسيط عام لمعالجة الأخطاء إلى مشروع Express قائم للتمييز بين الأخطاء التشغيلية (
isOperational) والأخطاء غير المعروفة؛ وبالنسبة للأخطاء غير المعروفة، إرجاع رسالة عامة دون الكشف عن تتبع المكدس. - استخدم
express-validatorلكتابة سلسلة تحقق كاملة لواجهة برمجة التطبيقات الخاصة بتسجيل المستخدم (اسم المستخدم، والبريد الإلكتروني، وقوة كلمة المرور، وتأكيد كلمة المرور)، وقم بإرجاع مصفوفة من الأخطاء على مستوى الحقول في حالة فشل عملية التحقق. - قم بتكوين multer لتمكين تحميل صور الملف الشخصي (ملف واحد، بحد أقصى 2 ميغابايت، بتنسيق JPG أو PNG فقط). قم بإرجاع عنوان URL للملف عند نجاح التحميل، وإرجاع السبب المحدد للفشل في حالة فشل التحميل.
- إضافة «express-rate-limit» لتطبيق الحد التدريجي لعدد الطلبات: 100 طلب إجمالاً كل 15 دقيقة، و30 طلبًا على واجهة برمجة التطبيقات (API) في الدقيقة، و5 محاولات تسجيل دخول فاشلة كل 15 دقيقة.
- قم بإنشاء ملف باسم
.env.exampleيتضمن قائمة بجميع متغيرات البيئة وقيمها الافتراضية، وتأكد من إضافة.envإلى.gitignore.