Menu
CoddyTech

Handler 404

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

Express menampilkan halamannya sendiri untuk path yang tidak dikenal, Cannot GET /x, yang tidak sesuai dengan situs maupun API-mu.

app.use((req, res) => {
    res.status(404).send('No forecast here. Try /weather/oslo');
});

Middleware tanpa path dijalankan untuk setiap permintaan yang mencapainya. Letakkan middleware ini setelah semua route dan setelah express.static: permintaan yang sudah ditangani tidak akan sampai ke sana, jadi middleware ini hanya melihat permintaan yang belum ditangani. Middleware ini menangani permintaan, jadi tidak memerlukan next.

req.path adalah path tanpa query string, jadi req.path.startsWith('/api/') membedakan panggilan API dari halaman.

challenge icon

Tantangan

Mudah
  • Path di bawah /api/ yang tidak ditangani apa pun akan mendapat 404 dengan { "error": "Not found" }.
  • Path lainnya akan menampilkan halaman HTML 404 yang bertuliskan Page not found.

Kirim GET /api/nope, buka /nope di tampilan halaman, lalu kirim GET /api/recipes/1.

Coba sendiri

import express from 'express';
import { randomUUID } from 'node:crypto';
import { recipes } from './data.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('<', '&lt;')
        .replaceAll('>', '&gt;')
        .replaceAll('"', '&quot;');

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.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.post('/api/recipes', (req, res) => {
    const { title, tags } = req.body;
    if (!title) {
        res.status(400).json({ error: 'A recipe needs a title' });
        return;
    }
    const recipe = { id: recipes.length + 1, title, tags: tags ?? [] };
    recipes.push(recipe);
    res.status(201).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');
});

Semua pelajaran di Dasar-Dasar Express

Latihan sendiri: Playground Express