Request و Response — REST APIs
الطلب والاستجابة (Request & Response)
شكل الطلب
POST /api/v1/books?notify=true HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer eyJhbGciOi...
{"title": "الأيام", "price": 12}سطر الطلب (الطريقة والمسار)، ثم الترويسات، ثم سطر فارغ، ثم الجسم.
شكل الاستجابة
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Location: /api/v1/books/4
{"id": 4, "title": "الأيام", "price": 12}Content-Type مهم
express.json() يقرأ الجسم فقط إذا كانت ترويسة الطلب Content-Type: application/json. إذا نسيها العميل، يصبح req.body فارغاً — من أشهر أسباب "البيانات لا تصل". النماذج التقليدية ترسل application/x-www-form-urlencoded (يقرؤها express.urlencoded())، ورفع الملفات multipart/form-data (درس رفع الملفات).
ما يصل من العميل
| المصدر | النوع في Express | ملاحظات |
|---|---|---|
| req.params | نصوص | من المسار |
| req.query | نصوص أو مصفوفات أو كائنات | ?tag=a&tag=b ← مصفوفة؛ ?price[gte]=5 ← كائن! |
| req.body | أي JSON | قد يحتوي حقولاً لم تتوقعها |
| req.headers | نصوص (أسماء بأحرف صغيرة) | |
كل ذلك تحت سيطرة العميل: لا تفترض أنواعاً ولا حقولاً. تحقق من كل شيء (درس التحقق)، ولا تمرر req.body كما هو لقاعدة البيانات (قد يحتوي role: "admin").
استجابات متسقة
const express = require("express");
const app = express();
app.use(express.json());
app.post("/api/v1/books", (req, res) => {
const { title, price } = req.body ?? {}; // خذ الحقول المسموحة فقط
if (typeof title !== "string" || title.trim() === "") {
return res.status(400).json({ error: { code: "VALIDATION_ERROR", message: "العنوان مطلوب" } });
}
const book = { id: 4, title: title.trim(), price: Number(price) || 0 };
res.status(201).location(`/api/v1/books/${book.id}`).json(book);
});- اختر شكلاً واحداً للأخطاء واستخدمه في كل مكان.
- لا ترسل حقولاً حساسة (password، passwordHash، رموز داخلية) في الاستجابات أبداً.
أدوات للتجربة
curl، أو امتداد Thunder Client / REST Client في VS Code، أو Postman/Bruno. مثال:
curl -X POST http://localhost:3000/api/v1/books -H "Content-Type: application/json" -d '{"title":"الأيام"}'تمرين
أرسل طلب POST بلا ترويسة Content-Type ولاحظ req.body، ثم أرسل ?tag=a&tag=b واطبع req.query.tag ونوعه.
const express = require('express');
const app = express();
app.use(express.json());
// ===== Request Examples =====
app.get('/api/users/:id', (req, res) => {
// URL: /api/users/123?fields=name,email
console.log('URL Params:', req.params); // { id: '123' }
console.log('Query:', req.query); // { fields: 'name,email' }
console.log('Headers:', req.headers); // Authorization, Content-Type, etc.
console.log('Method:', req.method); // GET
console.log('Path:', req.path); // /api/users/123
});
app.post('/api/users', (req, res) => {
console.log('Body:', req.body); // { name: 'أحمد', email: '...' }
});
// ===== Response Examples =====
// JSON Response
app.get('/api/data', (req, res) => {
res.json({
success: true,
data: { message: 'مرحباً' }
});
});
// Status + JSON
app.post('/api/users', (req, res) => {
const user = createUser(req.body);
res.status(201).json({
success: true,
data: user
});
});
// Error Response
app.get('/api/users/:id', (req, res) => {
const user = findUser(req.params.id);
if (!user) {
return res.status(404).json({
success: false,
error: {
code: 'USER_NOT_FOUND',
message: 'المستخدم غير موجود'
}
});
}
res.json({ success: true, data: user });
});
// Response Headers
app.get('/api/download', (req, res) => {
res.set({
'Content-Type': 'application/pdf',
'Content-Disposition': 'attachment; filename=file.pdf'
});
res.send(fileBuffer);
});
// Redirect
app.get('/old-route', (req, res) => {
res.redirect(301, '/new-route');
});اختبر فهمك