الترتيب مهم
جزء من قسم أساسيات 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.
التحدي
سهليحصل كل رد على معرّف، حتى تتمكن من العثور على طلب الزائر في السجل عندما يُبلغ عن مشكلة. يجب أن يتضمن كل رد معرّفًا: الصفحات وبيانات API والملفات التي يرسلها public.
- في
server.js، استوردrandomUUIDمنnode:crypto. يعيد كل استدعاء معرّفًا عشوائيًا جديدًا، مثل3b241101-e2bb-4255-8caf-4136c566a962. - أضف برمجية وسيطة تضبط ترويسة استجابة باسم
X-Request-Id، وتضع فيها قيمة جديدة منrandomUUID()لكل طلب، ثم تستدعيnext(). - ضعها بحيث يمر كل طلب عبرها، بما في ذلك الملفات التي يرسلها
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