Menu
CoddyTech

الترتيب مهم

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

يشغّل Express البرمجيات الوسيطة والمسارات بالترتيب الذي يضيفها بهما الكود، وينهي المسار الذي يرسل إجابةً الطلب عنده.

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

app.use((req, res, next) => {
    res.set('Cache-Control', 'no-store');
    next();
});

تضيف res.set(name, value) ترويسةً إلى الاستجابة، ويجب تشغيلها قبل إرسال الاستجابة. هنا، يجيب GET /bikes قبل الوصول إلى البرمجية الوسيطة، لذا لا تحصل استجابته على الترويسة مطلقًا: لا ترى البرمجية الوسيطة إلا الطلبات التي لم يُجب عنها أي مسار أعلاها. ضع البرمجيات الوسيطة التي ينبغي أن تتعامل مع كل استجابة فوق المسارات وفوق express.static.

challenge icon

التحدي

سهل

يحصل كل رد على معرّف، حتى تتمكن من العثور على طلب الزائر في السجل عندما يُبلغ عن مشكلة. يجب أن يتضمن كل رد معرّفًا: الصفحات وبيانات API والملفات التي يرسلها public.

  1. في server.js، استورد randomUUID من node:crypto. يعيد كل استدعاء معرّفًا عشوائيًا جديدًا، مثل 3b241101-e2bb-4255-8caf-4136c566a962.
  2. أضف برمجية وسيطة تضبط ترويسة استجابة باسم X-Request-Id، وتضع فيها قيمة جديدة من randomUUID() لكل طلب، ثم تستدعي next().
  3. ضعها بحيث يمر كل طلب عبرها، بما في ذلك الملفات التي يرسلها express.static. لا يحصل الرد على الترويسة إلا إذا نُفذت البرمجية الوسيطة قبل أن يرسل شيءٌ ما الرد.

جرّب ذلك: اضغط «العب» وأرسل /api/recipes مرتين. في علامة التبويب Headers، تكون قيمة X-Request-Id مختلفة في كل مرة. أرسل /style.css و/: سيحمل كل منهما أيضًا ترويسة X-Request-Id.

جرّب بنفسك

import { escapeHtml } from './html.js';
import express from 'express';
import { recipes } from './data.js';

const app = express();

app.use((req, res, next) => {
    console.log(`${req.method} ${req.url}`);
    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');
});

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

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