오류 처리
Coddy Node.js 여정의 Express 기초 섹션에 포함된 레슨. 23개 중 18번째.
라우트에서 예외가 발생하면 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 처리기 뒤에 오류 처리기를 마지막에 추가하세요. 세부 정보는 개발자를 위해 로그에 기록하고, 클라이언트에는 짧은 메시지를 보내세요.
throw new Error('text')는 라우트를 중단하고 오류를 Express에 전달하며, next(err)를 호출해도 같은 일이 일어납니다. 방문자가 잘못된 정보를 보냈고 수정할 수 있는 경우에는 400으로 응답하세요. 서버에 문제가 있을 때는 예외를 발생시키세요.
챌린지
쉬움Recipe Box를 계속 진행하세요. 별도의 practice-errors/ 파일은 무시해도 됩니다. 이제 ‘실행’을 누르면 프로젝트의 server.js가 시작됩니다.
요리하는 사람들은 파티를 위해 제공량을 두 배로 늘리는 등 레시피의 양을 조절하고 싶어 합니다. 제공량을 저장하지 않은 레시피는 양을 조절할 수 없습니다. 이는 방문자가 해결할 수 있는 문제가 아니라 데이터의 오류이므로, 깔끔한 JSON 500 응답을 보내는 오류 처리기로 넘어갑니다.
server.js에서 404 처리기 위에/api/recipes/:id/scale경로를 추가하세요./api/recipes/1/scale?by=2와 같이 쿼리 문자열에서 배수를 읽습니다.- 해당 id의 레시피가 없으면 상태 코드 404와
{"error": "No such recipe"}를 응답하세요. by를 숫자로 변환하세요. 숫자가 아니거나 0 이하이면 상태 코드 400과{"error": "Scale by a positive number"}를 응답하세요.Number('lots')는NaN이며,Number.isNaN으로 이를 확인할 수 있습니다.- 레시피에
servings가 없으면 메시지가Recipe 5 has no servings인 새Error를 throw하세요. 여기서 5 대신 해당 레시피의 id를 넣으세요. - 그렇지 않으면 제목과 양을 조절한 제공량만 담은 JSON을 응답하세요. 레시피 1인 Tomato Soup의 제공량은 4이므로
/api/recipes/1/scale?by=2는{"title": "Tomato Soup", "servings": 8}를 보냅니다. 다른 필드는 추가하지 마세요. - 파일의 미들웨어 끝부분에서 404 처리기 뒤에 오류 처리기를 추가하세요. 이 처리기는 네 개의 인수
(err, req, res, next)를 받는 함수가 전달된app.use입니다.console.error로 오류를 출력하고 상태 코드 500과{"error": "Something went wrong"}를 응답하세요.
직접 해 보기: ‘실행’을 누르고 /api/recipes/1/scale?by=2를 보내세요. {"title":"Tomato Soup","servings":8}가 표시되어야 합니다. /api/recipes/2/scale?by=lots를 보내면 상태 코드 400과 {"error":"Scale by a positive number"}가 표시됩니다. /api/recipes/99/scale?by=2를 보내면 상태 코드 404가 표시됩니다. 이제 POST를 선택하고 {"title": "Toast"}를 /api/recipes로 보내세요. 제공량이 없는 레시피 5가 됩니다. /api/recipes/5/scale?by=2를 보내면 상태 코드 500과 {"error":"Something went wrong"}가 표시되고, 콘솔에는 오류 Recipe 5 has no servings가 표시됩니다.
직접 해보기
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')는 일반 텍스트를 전송하므로 각 제목이 한 줄씩 표시됩니다.
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');
});
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
Express 기초의 모든 레슨
직접 연습해 보세요: Express 플레이그라운드