تصميم APIs قوية مع Node.js و Express

تصميم APIs قوية مع Node.js و Express

Ahmad BarishAhmad Barish
7 دقيقة للقراءة

تعلم كيفية بناء APIs قابلة للتوسع وآمنة باستخدام Node.js و Express مع أفضل الممارسات والأنماط المعمارية.

7 دقيقة للقراءة
📝1,104 كلمة
0 مشاهدة
# تصميم APIs قوية مع Node.js و Express بناء APIs جيدة يتطلب فهماً عميقاً للمتطلبات والمستخدمين. في هذا المقال، سنغطي أساسيات تصميم APIs القوية. ## 1. هيكل المشروع المناسب ``` src/ ├── config/ │ ├── database.js │ └── environment.js ├── controllers/ │ ├── userController.js │ └── authController.js ├── middleware/ │ ├── auth.js │ ├── validation.js │ └── errorHandler.js ├── models/ │ ├── User.js │ └── index.js ├── routes/ │ ├── users.js │ ├── auth.js │ └── index.js ├── services/ │ ├── userService.js │ └── emailService.js ├── utils/ │ ├── logger.js │ └── helpers.js ├── app.js └── server.js ``` ## 2. إعداد Express الأساسي ```javascript // app.js const express = require('express'); const cors = require('cors'); const helmet = require('helmet'); const compression = require('compression'); const app = express(); // Middleware الأمان app.use(helmet()); app.use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'], credentials: true })); // ضغط الاستجابات app.use(compression()); // تحليل JSON app.use(express.json({ limit: '10mb' })); app.use(express.urlencoded({ extended: true })); // Logging app.use((req, res, next) => { console.log(`${new Date().toISOString()} - ${req.method} ${req.path}`); next(); }); module.exports = app; ``` ## 3. نظام التوجيه (Routing) ```javascript // routes/users.js const express = require('express'); const router = express.Router(); const userController = require('../controllers/userController'); const { authenticate, authorize } = require('../middleware/auth'); const { validateUser } = require('../middleware/validation'); // GET /users - جلب جميع المستخدمين router.get('/', authenticate, authorize(['admin']), userController.getAllUsers ); // GET /users/:id - جلب مستخدم محدد router.get('/:id', authenticate, userController.getUserById ); // POST /users - إنشاء مستخدم جديد router.post('/', validateUser, userController.createUser ); // PUT /users/:id - تحديث مستخدم router.put('/:id', authenticate, validateUser, userController.updateUser ); // DELETE /users/:id - حذف مستخدم router.delete('/:id', authenticate, authorize(['admin']), userController.deleteUser ); module.exports = router; ``` ## 4. Controllers منظمة ```javascript // controllers/userController.js const userService = require('../services/userService'); const asyncHandler = require('../middleware/asyncHandler'); exports.getAllUsers = asyncHandler(async (req, res) => { const { page = 1, limit = 10, search } = req.query; const result = await userService.getUsers({ page: parseInt(page), limit: parseInt(limit), search }); res.json({ success: true, data: result.users, pagination: { page: result.page, limit: result.limit, total: result.total, pages: Math.ceil(result.total / result.limit) } }); }); exports.getUserById = asyncHandler(async (req, res) => { const { id } = req.params; const user = await userService.getUserById(id); if (!user) { return res.status(404).json({ success: false, error: 'User not found' }); } res.json({ success: true, data: user }); }); exports.createUser = asyncHandler(async (req, res) => { const userData = req.body; const user = await userService.createUser(userData); res.status(201).json({ success: true, data: user }); }); exports.updateUser = asyncHandler(async (req, res) => { const { id } = req.params; const updateData = req.body; const user = await userService.updateUser(id, updateData); if (!user) { return res.status(404).json({ success: false, error: 'User not found' }); } res.json({ success: true, data: user }); }); exports.deleteUser = asyncHandler(async (req, res) => { const { id } = req.params; const deleted = await userService.deleteUser(id); if (!deleted) { return res.status(404).json({ success: false, error: 'User not found' }); } res.status(204).send(); }); ``` ## 5. التحقق من صحة البيانات (Validation) ```javascript // middleware/validation.js const Joi = require('joi'); const userSchema = Joi.object({ name: Joi.string().min(2).max(50).required(), email: Joi.string().email().required(), password: Joi.string().min(8).pattern(new RegExp('^[a-zA-Z0-9]{8,}$')).required(), role: Joi.string().valid('user', 'admin').default('user'), age: Joi.number().integer().min(13).max(120) }); const validateUser = (req, res, next) => { const { error } = userSchema.validate(req.body, { abortEarly: false }); if (error) { const errors = error.details.map(detail => ({ field: detail.path.join('.'), message: detail.message })); return res.status(400).json({ success: false, error: 'Validation failed', details: errors }); } next(); }; module.exports = { validateUser }; ``` ## 6. معالجة الأخطاء العامة ```javascript // middleware/errorHandler.js const errorHandler = (err, req, res, next) => { let error = { ...err }; error.message = err.message; // Log error console.error(err); // Mongoose bad ObjectId if (err.name === 'CastError') { const message = 'Resource not found'; error = { message, statusCode: 404 }; } // Mongoose duplicate key if (err.code === 11000) { const message = 'Duplicate field value entered'; error = { message, statusCode: 400 }; } // Mongoose validation error if (err.name === 'ValidationError') { const message = Object.values(err.errors).map(val => val.message).join(', '); error = { message, statusCode: 400 }; } res.status(error.statusCode || 500).json({ success: false, error: error.message || 'Server Error', ...(process.env.NODE_ENV === 'development' && { stack: err.stack }) }); }; module.exports = errorHandler; ``` ## 7. إدارة قاعدة البيانات مع Mongoose ```javascript // models/User.js const mongoose = require('mongoose'); const bcrypt = require('bcryptjs'); const userSchema = new mongoose.Schema({ name: { type: String, required: [true, 'Please add a name'], trim: true, maxlength: [50, 'Name cannot be more than 50 characters'] }, email: { type: String, required: [true, 'Please add an email'], unique: true, match: [ /^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/, 'Please add a valid email' ] }, password: { type: String, required: [true, 'Please add a password'], minlength: 8, select: false }, role: { type: String, enum: ['user', 'admin'], default: 'user' }, avatar: { type: String, default: null }, isActive: { type: Boolean, default: true } }, { timestamps: true }); // Encrypt password before saving userSchema.pre('save', async function(next) { if (!this.isModified('password')) { next(); } const salt = await bcrypt.genSalt(10); this.password = await bcrypt.hash(this.password, salt); }); // Match password userSchema.methods.matchPassword = async function(enteredPassword) { return await bcrypt.compare(enteredPassword, this.password); }); module.exports = mongoose.model('User', userSchema); ``` ## 8. Services Layer ```javascript // services/userService.js const User = require('../models/User'); class UserService { async getUsers({ page = 1, limit = 10, search = '' }) { const skip = (page - 1) * limit; let query = { isActive: true }; if (search) { query.$or = [ { name: { $regex: search, $options: 'i' } }, { email: { $regex: search, $options: 'i' } } ]; } const users = await User.find(query) .skip(skip) .limit(limit) .select('-password') .sort({ createdAt: -1 }); const total = await User.countDocuments(query); return { users, page, limit, total, pages: Math.ceil(total / limit) }; } async getUserById(id) { return await User.findById(id).select('-password'); } async createUser(userData) { const user = await User.create(userData); return await User.findById(user._id).select('-password'); } async updateUser(id, updateData) { return await User.findByIdAndUpdate(id, updateData, { new: true, runValidators: true }).select('-password'); } async deleteUser(id) { return await User.findByIdAndUpdate(id, { isActive: false }); } } module.exports = new UserService(); ``` ## 9. معالجة الأخطاء غير المتزامنة ```javascript // middleware/asyncHandler.js const asyncHandler = (fn) => (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next); module.exports = asyncHandler; ``` ## 10. إعداد الخادم النهائي ```javascript // server.js const app = require('./src/app'); const connectDB = require('./src/config/database'); const PORT = process.env.PORT || 5000; // الاتصال بقاعدة البيانات connectDB(); // بدء الخادم const server = app.listen(PORT, () => { console.log(`Server running in ${process.env.NODE_ENV} mode on port ${PORT}`); }); // معالجة الأخطاء غير المعالجة process.on('unhandledRejection', (err, promise) => { console.log(`Error: ${err.message}`); server.close(() => { process.exit(1); }); }); ``` ## خاتمة بناء APIs قوية يتطلب التركيز على عدة جوانب: الأمان، الأداء، قابلية الصيانة، والتوثيق. ابدأ بتصميم واضح للموارد والعمليات، ثم ركز على تنفيذ نظيف ومنظم. تذكر: API جيد ليس فقط يعمل، بل يسهل استخدامه وتوسيعه!