Menu
CoddyTech

오류 처리

Coddy Node.js 여정의 Express 기초 섹션에 포함된 레슨. 20개 중 15번째.

라우트에서 예외가 발생하면 Express는 자체 오류 페이지로 응답합니다. 이 페이지는 JSON이 아니며, 여러분을 위한 세부 정보를 표시합니다.

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' });
});

인수가 네 개인 미들웨어 (err, req, res, next)는 오류 처리기입니다. Express는 인수의 개수를 확인합니다. 인수가 세 개면 일반 미들웨어이며, 오류가 발생하면 건너뜁니다. 라우트에서 예외가 발생하면 Express는 일반 미들웨어를 건너뛰고 오류와 함께 오류 처리기를 호출합니다. 404 처리기 뒤에 마지막으로 추가하세요. 세부 정보는 여러분이 확인할 수 있도록 기록하고, 클라이언트에는 짧은 메시지를 보내세요.

잘못된 ?by=는 방문자의 실수이므로 여전히 400 응답을 받습니다. 오류는 방문자가 해결할 수 없는 문제를 뜻합니다. 예를 들면 레시피를 인분 수 없이 저장한 경우입니다.

challenge icon

챌린지

쉬움
  • GET /api/recipes/:id/scale?by=2는 { "title", "servings" }를 반환하며, servings는 곱해집니다.
  • 양수가 아닌 계수: 400과 { "error": "Scale by a positive number" }를 반환합니다.
  • servings가 없는 레시피는 Recipe <id> has no servings 오류를 발생시킵니다.
  • 오류 처리기는 오류를 기록하고 500과 { "error": "Something went wrong" }를 반환합니다.

?by=2와 ?by=lots를 전송하세요. 그런 다음 { "title": "Toast" }를 /api/recipes에 전송하고 레시피 5의 양을 조정하세요.

직접 해보기

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('&', '&amp;')
        .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')는 일반 텍스트를 전송하므로 각 제목이 한 줄에 하나씩 표시됩니다.
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 icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

Express 기초의 모든 레슨