Menu
CoddyTech

Halaman Pencarian

Termasuk bagian Dasar-Dasar Express dalam perjalanan Node.js di Coddy. Pelajaran 23 dari 23.

Halaman pencarian adalah sebuah formulir, dan server menanggapinya seperti permintaan GET lainnya.

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

Mengirim formulir ini akan membawa browser ke /books?author= diikuti teks yang diketik pada kolom bernama author. Rute membacanya dari req.query. Menuliskan kembali kata tersebut sebagai value kolom akan membuatnya tetap terlihat setelah pencarian; kata itu berasal dari pengunjung, jadi lakukan escape.

challenge icon

Tantangan

Mudah

Recipe Box kini memiliki halaman pencarian. Pengunjung mengetik kata, lalu halaman menampilkan resep yang judulnya memuat kata tersebut.

  1. Di server.js, tambahkan rute async untuk /search di atas handler 404. Rute ini membaca kata dari req.query.q, atau menggunakan string kosong jika tidak ada.
  2. Halaman selalu memiliki formulir pencarian: <form action="/search" method="get"> dengan kolom teks bernama q dan sebuah tombol. Saat formulir dikirim, halaman membuka /search?q= diikuti kata tersebut.
  3. Kolom menampilkan kata yang dicari: setelah mencari curry, kolom berisi curry. Masukkan kata tersebut ke atribut value pada kolom, setelah melewatkannya melalui escapeHtml, karena kata itu berasal dari pengunjung.
  4. Di bawah formulir, tampilkan tautan untuk setiap resep dari loadRecipes() yang judulnya memuat kata tersebut, tanpa membedakan huruf besar dan kecil: soup dan SOUP sama-sama menemukan Tomato Soup. Setiap tautan menuju halaman resep dan menampilkan judulnya yang sudah di-escape, seperti pada halaman beranda: <a href="/recipes/1">Tomato Soup</a>.
  5. Jika sebuah kata telah dicari dan tidak ada judul yang memuatnya, tampilkan teks No recipes found sebagai pengganti daftar.

Coba: Tekan Run dan kirim /search: halaman menampilkan formulir kosong. Ketik SOUP di kolom lalu tekan tombolnya. Halaman menampilkan Tomato Soup, dan kolom tetap menampilkan SOUP. Cari pizza: No recipes found. Cari fish: Fish & Chips, ditampilkan dengan benar. Lalu lakukan POST {"title": "Pea Soup", "tags": ["soup"]} ke /api/recipes, kemudian cari soup lagi: Pea Soup juga ada dalam daftar.

Coba sendiri

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') mengirim teks biasa, sehingga setiap judul muncul di baris tersendiri.
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');
});

Semua pelajaran di Dasar-Dasar Express

Latihan sendiri: Playground Express