Menu
CoddyTech

비동기 라우트

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

서버가 다시 시작되면 배열은 사라집니다. 파일은 데이터를 보존합니다.

import { readFile } from 'node:fs/promises';

app.get('/api/forecast', async (req, res) => {
    const text = await readFile('data/forecast.json', 'utf8');
    res.json(JSON.parse(text));
});

라우트는 await와 함께 기다리는 async 함수가 될 수 있습니다. 여기서는 node:fs/promises의 readFile을 기다립니다. Express 5는 라우트가 반환하는 프라미스를 감시합니다. 파일이 없거나 JSON이 잘못되어 프라미스가 거부되면, throw가 발생했을 때처럼 오류가 오류 핸들러로 전달됩니다.

await가 없으면 데이터 대신 프라미스를 갖게 되고, res.json(promise)는 {}를 전송합니다. 새 data/recipes.json에는 네 가지 레시피가 들어 있습니다.

challenge icon

챌린지

쉬움

메모리에 있는 배열은 서버가 다시 시작될 때마다 사라집니다. 대신 레시피를 파일에 저장하세요. data/recipes.json에는 이미 레시피 네 개가 들어 있습니다.

  1. routes/recipes.js에서 node:fs/promises로부터 readFile과 writeFile을 가져오고, ../data.js에서 recipes를 가져오던 부분은 제거하세요.
  2. 도우미 함수 loadRecipes()를 하나 작성하세요. 이 비동기 함수는 data/recipes.json을 읽고 파싱한 목록을 반환합니다. 다음 수업에서는 이 이름으로 사용합니다.
  3. 파일의 모든 경로를 async로 만들고, 각 경로의 시작 부분에서 loadRecipes()를 기다리세요. 대상은 /tags, 목록, 레시피 하나, POST 경로, 수량 조정 경로입니다.
  4. POST 경로에서 새 레시피를 목록에 추가한 다음, writeFile을 사용해 목록 전체를 JSON 텍스트로 data/recipes.json에 다시 쓰세요.
  5. 수량 조정 경로는 여전히 제공량이 없는 레시피에서 오류를 발생시키지만, 이제 비동기 함수 안에서 발생합니다. Express 5는 이전과 마찬가지로 해당 오류를 오류 처리기로 전달하므로, 응답은 여전히 500과 {"error": "Something went wrong"}입니다. 경로에서 이 오류를 잡지 마세요.

직접 해 보세요: ‘플레이’를 누르고 /api/recipes로 요청을 보내세요. 파일에서 읽은 레시피 네 개가 이전과 동일하게 표시됩니다. POST를 선택하고 /api/recipes로 {"title": "Gazpacho", "tags": ["vegan", "soup"]}를 보내세요. 상태 코드는 201이고 "id": 5인 레시피가 반환됩니다. /api/recipes/5를 보내면 Gazpacho가 반환됩니다. /api/recipes/99를 보내면 상태 코드 404가 반환됩니다. 그런 다음 제공량이 없어 레시피 6이 되는 {"title": "Toast"}를 POST로 보내고, /api/recipes/6/scale?by=2를 보내세요. 상태 코드는 500이고 {"error":"Something went wrong"}가 반환됩니다. 데이터 대신 {}가 표시되면 await가 빠진 것입니다.

직접 해보기

import express from 'express';
import { recipes } from '../data.js';

const router = express.Router();

// 모든 태그를 중복 없이 알파벳순으로.
router.get('/tags', (req, res) => {
    const tags = new Set(recipes.flatMap((recipe) => recipe.tags));
    res.json([...tags].sort());
});

router.get('/', (req, res) => {
    res.json(recipes);
});

router.get('/: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);
});

router.post('/', (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);
});

router.get('/:id/scale', (req, res) => {
    const recipe = recipes.find((r) => r.id === Number(req.params.id));
    if (!recipe) {
        res.status(404).json({ error: 'No such recipe' });
        return;
    }
    const by = Number(req.query.by);
    if (!Number.isFinite(by) || by <= 0) {
        res.status(400).json({ error: 'Scale by a positive number' });
        return;
    }
    if (!recipe.servings) {
        throw new Error(`Recipe ${recipe.id} has no servings`);
    }
    res.json({ title: recipe.title, servings: recipe.servings * by });
});

export default router;
quiz icon실력 점검

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

Express 기초의 모든 레슨

직접 연습해 보세요: Express 플레이그라운드