Suchseite
Teil des Abschnitts Express-Grundlagen der Node.js-Journey von Coddy. Lektion 20 von 20.
Eine Suchseite ist ein Formular, und der Server beantwortet es wie jede andere GET-Anfrage.
<form action="/books" method="get">
<input name="author">
<button>Find</button>
</form>Beim Absenden dieses Formulars wird der Browser zu /books?author= weitergeleitet, gefolgt von dem, was in das Feld mit dem Namen author eingegeben wurde. Die Route liest den Wert aus req.query. Wenn du das Wort als value des Feldes zurückschreibst, bleibt es nach der Suche sichtbar; es stammt vom Besucher, also musst du es maskieren.
Aufgabe
EinfachGET /search?q=soupliefert eine Seite mit einem GET-Formular (Feldq, gesendet an/search) und Links zu den Titeln ausloadRecipes(), die das Wort unabhängig von der Groß- und Kleinschreibung enthalten.- Kein Treffer:
No recipes found. - Das Feld behält das Wort bei, nachdem es escaped wurde.
Öffne /search in der Seitenansicht und suche nach SOUP, dann nach pizza.
Probier es selbst
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'));
const escapeHtml = (text) =>
text
.replaceAll('&', '&')
.replaceAll('<', '<')
.replaceAll('>', '>')
.replaceAll('"', '"');
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') sendet reinen Text, sodass jeder Titel in einer eigenen Zeile steht.
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 date = req.query.date ? new Date(req.query.date) : new Date();
if (Number.isNaN(date.getTime())) {
res.status(400).json({ error: 'Use a date like 2026-03-01' });
return;
}
const day = Math.floor((date - Date.UTC(date.getUTCFullYear(), 0, 0)) / 86400000);
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');
});