Menu
CoddyTech

Penanganan error

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

Saat sebuah rute melempar error, Express menampilkan halaman error-nya sendiri, yang bukan JSON dan menampilkan detail yang ditujukan untukmu.

app.get('/api/fines/:member', (req, res) => {
    throw new Error('Fines database is down');
});

app.use((err, req, res, next) => {
    console.error(err);
    res.status(500).json({ error: 'Try again later' });
});

Middleware dengan empat argumen, (err, req, res, next), adalah penangan error. Express menghitung jumlah argumen: dengan tiga argumen, middleware tersebut adalah middleware biasa dan akan dilewati saat terjadi error. Saat sebuah rute melempar error, Express melewati middleware biasa dan memanggil penangan error dengan error tersebut. Tambahkan penangan error ini terakhir, setelah penangan 404. Catat detailnya untuk dirimu sendiri, lalu kirim pesan singkat kepada client.

throw new Error('text') menghentikan rute dan meneruskan error ke Express, begitu juga dengan pemanggilan next(err). Berikan respons 400 saat pengunjung mengirim sesuatu yang salah dan bisa memperbaikinya. Lempar error saat server yang bermasalah.

challenge icon

Tantangan

Mudah

Lanjutkan Recipe Box. Kamu bisa mengabaikan file practice-errors/ yang terpisah; tombol Jalankan sekarang memulai server.js proyek.

Koki ingin menyesuaikan skala resep, misalnya menggandakan jumlah porsi untuk pesta. Resep yang disimpan tanpa jumlah porsi tidak bisa disesuaikan skalanya. Itu adalah kesalahan pada datamu, bukan sesuatu yang bisa diperbaiki pengunjung, jadi kesalahan itu diteruskan ke penangan error yang mengirim JSON 500 yang rapi.

  1. Di server.js, tambahkan rute untuk /api/recipes/:id/scale di atas penangan 404. Rute ini membaca faktor dari string query, seperti pada /api/recipes/1/scale?by=2.
  2. Jika tidak ada resep dengan id tersebut, berikan status 404 dan {"error": "No such recipe"}.
  3. Ubah by menjadi angka. Jika nilainya bukan angka, atau 0 atau kurang, berikan status 400 dan {"error": "Scale by a positive number"}. Number('lots') menghasilkan NaN, dan Number.isNaN dapat mendeteksinya.
  4. Jika resep tidak memiliki servings, lempar Error baru dengan pesan Recipe 5 has no servings, dengan id resep tersebut menggantikan 5.
  5. Jika tidak, berikan JSON yang hanya berisi judul dan jumlah porsi yang telah disesuaikan skalanya. Resep 1, Tomato Soup, memiliki 4 porsi, jadi /api/recipes/1/scale?by=2 mengirim {"title": "Tomato Soup", "servings": 8}. Jangan tambahkan field lain.
  6. Di akhir middleware file, setelah penangan 404, tambahkan penangan error: sebuah app.use dengan fungsi yang menerima empat argumen, (err, req, res, next). Fungsi ini mencetak error dengan console.error dan memberikan status 500 serta {"error": "Something went wrong"}.

Coba: Tekan Jalankan dan kirim /api/recipes/1/scale?by=2. Kamu seharusnya melihat {"title":"Tomato Soup","servings":8}. Kirim /api/recipes/2/scale?by=lots: status 400 dan {"error":"Scale by a positive number"}. Kirim /api/recipes/99/scale?by=2: status 404. Sekarang pilih POST dan kirim {"title": "Toast"} ke /api/recipes. Resep itu menjadi resep 5, tanpa jumlah porsi. Kirim /api/recipes/5/scale?by=2: status 500 dan {"error":"Something went wrong"}, dan Console menampilkan error Recipe 5 has no servings.

Coba sendiri

import { escapeHtml } from './html.js';
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'));

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 tampil 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.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.listen(3000, () => {
    console.log('Recipe Box is running on port 3000');
});
quiz iconUji dirimu

Pelajaran ini berisi kuis singkat. Mulai pelajarannya untuk menjawab kuis dan melacak progresmu.

Semua pelajaran di Dasar-Dasar Express

Latihan sendiri: Playground Express