Kodax

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');
});
اختبر فهمك