Menu
CoddyTech

דף החיפוש

חלק מהיחידה יסודות Express במסלול ה-Node.js של Coddy. שיעור 23 מתוך 23.

דף חיפוש הוא טופס, והשרת משיב לו כמו לכל בקשת GET אחרת.

<form action="/books" method="get">
    <input name="author">
    <button>Find</button>
</form>

שליחת הטופס הזה מעבירה את הדפדפן אל /books?author= ואחריו הטקסט שהוקלד בשדה ששמו author. הנתיב קורא אותו מתוך req.query. כתיבת המילה בחזרה אל value של השדה משאירה אותה גלויה לאחר החיפוש; היא הגיעה מהמבקר, לכן יש לבצע לה escaping.

challenge icon

אתגר

קל

Recipe Box מקבל דף חיפוש. מבקר מקליד מילה, והדף מציג את המתכונים שהכותרת שלהם מכילה אותה.

  1. ב-server.js, מעל מטפל ה-404, הוסף נתיב אסינכרוני עבור /search. הוא קורא את המילה מתוך req.query.q, או משתמש במחרוזת ריקה כשאין מילה.
  2. בדף תמיד מופיע טופס חיפוש: <form action="/search" method="get"> עם שדה טקסט בשם q ולחצן. שליחתו פותחת את /search?q= ואחריו המילה.
  3. השדה מציג את המילה שחיפשו: אחרי חיפוש של curry, השדה מכיל את curry. כתוב את המילה במאפיין value של השדה, לאחר העברתה דרך escapeHtml, מכיוון שהיא הגיעה מהמבקר.
  4. מתחת לטופס, הצג קישור לכל מתכון מתוך loadRecipes() שהכותרת שלו מכילה את המילה, ללא תלות באותיות גדולות או קטנות: גם soup וגם SOUP מוצאים את Tomato Soup. כל קישור מוביל לדף המתכון ומציג את הכותרת שלו לאחר ביצוע escaping, כמו בדף הבית: <a href="/recipes/1">Tomato Soup</a>.
  5. אם חיפשו מילה ושום כותרת לא מכילה אותה, הצג את הטקסט No recipes found במקום הרשימה.

נסה זאת: לחץ על Run ושלח בקשה אל /search: הדף מציג טופס ריק. הקלד SOUP בשדה ולחץ על הלחצן. הדף מציג את Tomato Soup, והשדה עדיין מציג את SOUP. חפש pizza: לא נמצאו מתכונים. חפש fish: Fish & Chips, ומוצג כראוי. לאחר מכן שלח בקשת POST אל /api/recipes עם {"title": "Pea Soup", "tags": ["soup"]} וחפש שוב soup: גם Pea Soup מופיע ברשימה.

נסו בעצמכם

import { escapeHtml } from './html.js';
import { dayOfYear } from './day-of-year.js';
import express from 'express';
import { randomUUID } from 'node:crypto';
import { recipes } from './data.js';
import recipesRouter, { loadRecipes } from './routes/recipes.js';
import reviewsRouter from './routes/reviews.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.json());

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.use('/api/recipes', recipesRouter);
app.use('/api/recipes/:id/reviews', reviewsRouter);

app.get('/api/today', async (req, res) => {
    const day = dayOfYear(req.query.date);
    if (day === null) {
        res.status(400).json({ error: 'Use a date like 2026-03-01' });
        return;
    }
    const recipes = await loadRecipes();
    res.json(recipes[day % recipes.length]);
});

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.use((req, res) => {
    if (req.path.startsWith('/api/')) {
        res.status(404).json({ error: 'Not found' });
        return;
    }
    res.status(404).send('<h1>Page not found</h1><p><a href="/">Back to Recipe Box</a></p>');
});

app.use((err, req, res, next) => {
    console.error(err);
    res.status(500).json({ error: 'Something went wrong' });
});

app.listen(3000, () => {
    console.log('Recipe Box is running on port 3000');
});

כל השיעורים ביחידה יסודות Express

תרגלו בעצמכם: Express אונליין