تصميم APIs قوية مع Node.js و Express
تعلم كيفية بناء 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 جيد ليس فقط يعمل، بل يسهل استخدامه وتوسيعه!