Menu
CoddyTech

قراءة محتوى الطلب

جزء من قسم أساسيات Express في رحلة Node.js على Coddy. الدرس 15 من 23.

يرسل طلب POST البيانات في متن الطلب، ولا يقرأ Express المتن إلا إذا طلبت منه ذلك.

app.use(express.json());

app.post('/api/loans', (req, res) => {
    const { bookId, member } = req.body;
    res.status(201).json({ bookId, member, due: '2026-11-08' });
});

يطلب GET شيئًا ما؛ أما POST فيرسل شيئًا ليتم إنشاؤه. إن express.json() وسيط يقرأ كل متن JSON ويضعه في req.body. من دونه تكون قيمة req.body هي undefined. يرد المسار الذي ينشئ شيئًا ما برمز الحالة 201 مع ما أنشأه.

تحقق من المتن قبل حفظه. إذا كان أحد الحقول مفقودًا، فهذا خطأ من العميل، لذا أرسل ردًا برمز الحالة 400 ولا تحفظ أي شيء.

challenge icon

التحدي

سهل

يمكن للزوار إضافة وصفاتهم الخاصة. يرسل برنامج الوصفة الجديدة بصيغة JSON في جسم طلب POST، وتحفظها واجهة API الخاصة بك.

  1. في server.js، أضف express.json() مع أسطر app.use الأخرى، قبل المسارات، لكي يصل جسم JSON إلى req.body.
  2. أضف مسارًا لطلبات POST إلى /api/recipes. يقرأ title وtags من الجسم، مثلًا {"title": "Miso Soup", "tags": ["vegan", "soup"]}.
  3. أنشئ الوصفة الجديدة باستخدام رقم المعرّف التالي: للإنشاءات الأربع معرّفات من 1 إلى 4، لذا تحصل أول وصفة جديدة على المعرّف 5. احتفظ بالعنوان والوسوم اللذين أُرسلا. إذا لم تُرسل tags، فاستخدم قائمة فارغة، [].
  4. أضف الوصفة إلى مصفوفة recipes، لكي يعرضها GET /api/recipes. ثم أرسل استجابة بالحالة 201 والوصفة الجديدة بصيغة JSON: {"id": 5, "title": "Miso Soup", "tags": ["vegan", "soup"]}
  5. عندما لا يحتوي الجسم على title، لا تحفظ شيئًا، وأرسل استجابة بالحالة 400 وJSON التالي: {"error": "A recipe needs a title"}

جرّب ذلك: اضغط على «تشغيل». اختر POST في محدد الطريقة، واكتب /api/recipes في خانة المسار، وضع {"title": "Miso Soup", "tags": ["vegan", "soup"]} في خانة الجسم. ينبغي أن ترى الحالة 201 والوصفة التي تتضمن "id": 5. اختر GET وأرسل /api/recipes: ست وصفات، وتكون Miso Soup الأخيرة. ثم أرسل POST مع {"tags": ["dinner"]} إلى /api/recipes: الحالة 400 و{"error":"A recipe needs a title"}. يظل GET /api/recipes يعرض خمس وصفات.

جرّب بنفسك

import { escapeHtml } from './html.js';
import express from 'express';
import { randomUUID } from 'node:crypto';
import { recipes } from './data.js';

const app = express();

app.use((req, res, next) => {
    console.log(`${req.method} ${req.url}`);
    next();
});

app.use((req, res, next) => {
    res.set('X-Request-Id', randomUUID());
    next();
});

app.use(express.static('public'));

app.get('/', (req, res) => {
    const items = recipes.map((r) => `<li><a href="/recipes/${r.id}">${escapeHtml(r.title)}</a></li>`);
    res.send(`<link rel="stylesheet" href="/style.css"><h1>Recipe Box</h1><ul>${items.join('')}</ul>`);
});

// ترسل type('text') نصًا عاديًا، لذا يظهر كل عنوان في سطر منفصل.
app.get('/recipes', (req, res) => {
    const { tag } = req.query;
    const list = tag ? recipes.filter((recipe) => recipe.tags.includes(tag)) : recipes;
    res.type('text').send(list.map((recipe) => recipe.title).join('\n'));
});

app.get('/recipes/:id', (req, res) => {
    const recipe = recipes.find((r) => r.id === Number(req.params.id));
    if (!recipe) {
        res.status(404).send('No such recipe');
        return;
    }
    res.send(recipe.title);
});

app.get('/api/recipes', (req, res) => {
    res.json(recipes);
});

app.get('/api/recipes/:id', (req, res) => {
    const recipe = recipes.find((r) => r.id === Number(req.params.id));
    if (!recipe) {
        res.status(404).json({ error: 'No such recipe' });
        return;
    }
    res.json(recipe);
});

app.get('/about', (req, res) => {
    res.send('Recipe Box collects family recipes.');
});

app.get('/menu', (req, res) => {
    res.redirect(301, '/recipes');
});

app.get('/menu/:id', (req, res) => {
    res.redirect(301, `/recipes/${req.params.id}`);
});

app.listen(3000, () => {
    console.log('Recipe Box is running on port 3000');
});
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس أساسيات Express

تدرّب بنفسك: Playground لـ Express